]> git.ipfire.org Git - people/ms/pakfire.git/blob - python/pakfire/cli.py
Implement distro-sync.
[people/ms/pakfire.git] / python / pakfire / cli.py
1 #!/usr/bin/python
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 os
25 import shutil
26 import sys
27 import tempfile
28
29 import base
30 import client
31 import config
32 import daemon
33 import logger
34 import packages
35 import repository
36 import server
37 import transaction
38 import util
39
40 from system import system
41 from constants import *
42 from i18n import _
43
44 # Initialize a very simple logging that is removed when a Pakfire instance
45 # is started.
46 logger.setup_logging()
47
48 class Cli(object):
49 pakfire = base.Pakfire
50
51 def __init__(self):
52 self.parser = argparse.ArgumentParser(
53 description = _("Pakfire command line interface."),
54 )
55
56 self.parse_common_arguments()
57
58 self.parser.add_argument("--root", metavar="PATH",
59 default="/",
60 help=_("The path where pakfire should operate in."))
61
62 # Add sub-commands.
63 self.sub_commands = self.parser.add_subparsers()
64
65 self.parse_command_install()
66 self.parse_command_reinstall()
67 self.parse_command_remove()
68 self.parse_command_info()
69 self.parse_command_search()
70 self.parse_command_check_update()
71 self.parse_command_distro_sync()
72 self.parse_command_update()
73 self.parse_command_downgrade()
74 self.parse_command_provides()
75 self.parse_command_grouplist()
76 self.parse_command_groupinstall()
77 self.parse_command_repolist()
78 self.parse_command_clean()
79 self.parse_command_check()
80 self.parse_command_resolvdep()
81 self.parse_command_extract()
82
83 # Finally parse all arguments from the command line and save them.
84 self.args = self.parser.parse_args()
85
86 self.action2func = {
87 "install" : self.handle_install,
88 "reinstall" : self.handle_reinstall,
89 "remove" : self.handle_remove,
90 "check_update" : self.handle_check_update,
91 "distro_sync" : self.handle_distro_sync,
92 "update" : self.handle_update,
93 "downgrade" : self.handle_downgrade,
94 "info" : self.handle_info,
95 "search" : self.handle_search,
96 "provides" : self.handle_provides,
97 "grouplist" : self.handle_grouplist,
98 "groupinstall" : self.handle_groupinstall,
99 "repolist" : self.handle_repolist,
100 "clean_all" : self.handle_clean_all,
101 "check" : self.handle_check,
102 "resolvdep" : self.handle_resolvdep,
103 "extract" : self.handle_extract,
104 }
105
106 @property
107 def pakfire_args(self):
108 ret = {}
109
110 if hasattr(self.args, "root"):
111 ret["path"] = self.args.root
112
113 if hasattr(self.args, "offline") and self.args.offline:
114 ret["downloader"] = {
115 "offline" : self.args.offline,
116 }
117
118 if hasattr(self.args, "config"):
119 ret["configs"] = self.args.config
120 else:
121 ret["configs"] = None
122
123 return ret
124
125 def create_pakfire(self, cls=None, **kwargs):
126 if cls is None:
127 cls = self.pakfire
128
129 args = self.pakfire_args
130 args.update(kwargs)
131
132 p = cls(**args)
133
134 # Disable repositories.
135 for repo in self.args.disable_repo:
136 p.repos.disable_repo(repo)
137
138 # Enable repositories.
139 for repo in self.args.enable_repo:
140 p.repos.enable_repo(repo)
141
142 return p
143
144 def parse_common_arguments(self, offline_switch=True):
145 self.parser.add_argument("--version", action="version",
146 version="%(prog)s " + PAKFIRE_VERSION)
147
148 self.parser.add_argument("-v", "--verbose", action="store_true",
149 help=_("Enable verbose output."))
150
151 self.parser.add_argument("-c", "--config", nargs="?",
152 help=_("Path to a configuration file to load."))
153
154 self.parser.add_argument("--disable-repo", nargs="*", metavar="REPO",
155 help=_("Disable a repository temporarily."), default=[])
156
157 self.parser.add_argument("--enable-repo", nargs="*", metavar="REPO",
158 help=_("Enable a repository temporarily."), default=[])
159
160 if offline_switch:
161 self.parser.add_argument("--offline", action="store_true",
162 help=_("Run pakfire in offline mode."))
163
164 def parse_command_install(self):
165 # Implement the "install" command.
166 sub_install = self.sub_commands.add_parser("install",
167 help=_("Install one or more packages to the system."))
168 sub_install.add_argument("package", nargs="+",
169 help=_("Give name of at least one package to install."))
170 sub_install.add_argument("--without-recommends", action="store_true",
171 help=_("Don't install recommended packages."))
172 sub_install.add_argument("action", action="store_const", const="install")
173
174 def parse_command_reinstall(self):
175 # Implement the "reinstall" command.
176 sub_install = self.sub_commands.add_parser("reinstall",
177 help=_("Reinstall one or more packages."))
178 sub_install.add_argument("package", nargs="+",
179 help=_("Give name of at least one package to reinstall."))
180 sub_install.add_argument("action", action="store_const", const="reinstall")
181
182 def parse_command_remove(self):
183 # Implement the "remove" command.
184 sub_remove = self.sub_commands.add_parser("remove",
185 help=_("Remove one or more packages from the system."))
186 sub_remove.add_argument("package", nargs="+",
187 help=_("Give name of at least one package to remove."))
188 sub_remove.add_argument("action", action="store_const", const="remove")
189
190 @staticmethod
191 def _parse_command_update(parser, package=True):
192 if package:
193 parser.add_argument("package", nargs="*",
194 help=_("Give a name of a package to update or leave emtpy for all."))
195
196 parser.add_argument("--exclude", "-x", nargs="+",
197 help=_("Exclude package from update."))
198 parser.add_argument("--allow-vendorchange", action="store_true",
199 help=_("Allow changing the vendor of packages."))
200 parser.add_argument("--disallow-archchange", action="store_true",
201 help=_("Disallow changing the architecture of packages."))
202
203 def parse_command_update(self):
204 # Implement the "update" command.
205 sub_update = self.sub_commands.add_parser("update",
206 help=_("Update the whole system or one specific package."))
207 sub_update.add_argument("action", action="store_const", const="update")
208 self._parse_command_update(sub_update)
209
210 def parse_command_distro_sync(self):
211 # Implement the "distro-sync" command.
212 sub_distro_sync = self.sub_commands.add_parser("distro-sync",
213 help=_("Sync all installed with the latest one in the distribution."))
214 sub_distro_sync.add_argument("action", action="store_const", const="distro_sync")
215 self._parse_command_update(sub_distro_sync, package=False)
216
217 def parse_command_check_update(self):
218 # Implement the "check-update" command.
219 sub_check_update = self.sub_commands.add_parser("check-update",
220 help=_("Check, if there are any updates available."))
221 sub_check_update.add_argument("action", action="store_const", const="check_update")
222 self._parse_command_update(sub_check_update)
223
224 def parse_command_downgrade(self):
225 # Implement the "downgrade" command.
226 sub_downgrade = self.sub_commands.add_parser("downgrade",
227 help=_("Downgrade one or more packages."))
228 sub_downgrade.add_argument("package", nargs="*",
229 help=_("Give a name of a package to downgrade."))
230 sub_downgrade.add_argument("--allow-vendorchange", action="store_true",
231 help=_("Allow changing the vendor of packages."))
232 sub_downgrade.add_argument("--disallow-archchange", action="store_true",
233 help=_("Disallow changing the architecture of packages."))
234 sub_downgrade.add_argument("action", action="store_const", const="downgrade")
235
236 def parse_command_info(self):
237 # Implement the "info" command.
238 sub_info = self.sub_commands.add_parser("info",
239 help=_("Print some information about the given package(s)."))
240 sub_info.add_argument("package", nargs="+",
241 help=_("Give at least the name of one package."))
242 sub_info.add_argument("action", action="store_const", const="info")
243
244 def parse_command_search(self):
245 # Implement the "search" command.
246 sub_search = self.sub_commands.add_parser("search",
247 help=_("Search for a given pattern."))
248 sub_search.add_argument("pattern",
249 help=_("A pattern to search for."))
250 sub_search.add_argument("action", action="store_const", const="search")
251
252 def parse_command_provides(self):
253 # Implement the "provides" command
254 sub_provides = self.sub_commands.add_parser("provides",
255 help=_("Get a list of packages that provide a given file or feature."))
256 sub_provides.add_argument("pattern", nargs="+",
257 help=_("File or feature to search for."))
258 sub_provides.add_argument("action", action="store_const", const="provides")
259
260 def parse_command_grouplist(self):
261 # Implement the "grouplist" command
262 sub_grouplist = self.sub_commands.add_parser("grouplist",
263 help=_("Get list of packages that belong to the given group."))
264 sub_grouplist.add_argument("group", nargs=1,
265 help=_("Group name to search for."))
266 sub_grouplist.add_argument("action", action="store_const", const="grouplist")
267
268 def parse_command_groupinstall(self):
269 # Implement the "grouplist" command
270 sub_groupinstall = self.sub_commands.add_parser("groupinstall",
271 help=_("Install all packages that belong to the given group."))
272 sub_groupinstall.add_argument("group", nargs=1,
273 help=_("Group name."))
274 sub_groupinstall.add_argument("action", action="store_const", const="groupinstall")
275
276 def parse_command_repolist(self):
277 # Implement the "repolist" command
278 sub_repolist = self.sub_commands.add_parser("repolist",
279 help=_("List all currently enabled repositories."))
280 sub_repolist.add_argument("action", action="store_const", const="repolist")
281
282 def parse_command_clean(self):
283 sub_clean = self.sub_commands.add_parser("clean", help=_("Cleanup commands."))
284
285 sub_clean_commands = sub_clean.add_subparsers()
286
287 self.parse_command_clean_all(sub_clean_commands)
288
289 def parse_command_clean_all(self, sub_commands):
290 sub_create = sub_commands.add_parser("all",
291 help=_("Cleanup all temporary files."))
292 sub_create.add_argument("action", action="store_const", const="clean_all")
293
294 def parse_command_check(self):
295 # Implement the "check" command
296 sub_check = self.sub_commands.add_parser("check",
297 help=_("Check the system for any errors."))
298 sub_check.add_argument("action", action="store_const", const="check")
299
300 def parse_command_resolvdep(self):
301 # Implement the "resolvdep" command.
302 sub_resolvdep = self.sub_commands.add_parser("resolvdep",
303 help=_("Check the dependencies for a particular package."))
304 sub_resolvdep.add_argument("package", nargs=1,
305 help=_("Give name of at least one package to check."))
306 sub_resolvdep.add_argument("action", action="store_const", const="resolvdep")
307
308 def parse_command_extract(self):
309 # Implement the "extract" command.
310 sub_extract = self.sub_commands.add_parser("extract",
311 help=_("Extract a package to a directory."))
312 sub_extract.add_argument("package", nargs="+",
313 help=_("Give name of the file to extract."))
314 sub_extract.add_argument("--target", nargs="?",
315 help=_("Target directory where to extract to."))
316 sub_extract.add_argument("action", action="store_const", const="extract")
317
318 def run(self):
319 action = self.args.action
320
321 try:
322 func = self.action2func[action]
323 except KeyError:
324 raise Exception, "Unhandled action: %s" % action
325
326 return func()
327
328 def handle_info(self, long=False):
329 p = self.create_pakfire()
330
331 for pkg in p.info(self.args.package):
332 print pkg.dump(long=long)
333
334 def handle_search(self):
335 p = self.create_pakfire()
336
337 for pkg in p.search(self.args.pattern):
338 print pkg.dump(short=True)
339
340 def handle_update(self, **args):
341 p = self.create_pakfire()
342
343 packages = getattr(self.args, "package", [])
344
345 args.update({
346 "allow_archchange" : not self.args.disallow_archchange,
347 "allow_vendorchange" : self.args.allow_vendorchange,
348 "excludes" : self.args.exclude,
349 })
350
351 p.update(packages, **args)
352
353 def handle_distro_sync(self):
354 self.handle_update(sync=True)
355
356 def handle_check_update(self):
357 self.handle_update(check=True)
358
359 def handle_downgrade(self, **args):
360 p = self.create_pakfire()
361 p.downgrade(
362 self.args.package,
363 allow_vendorchange=self.args.allow_vendorchange,
364 allow_archchange=not self.args.disallow_archchange,
365 **args
366 )
367
368 def handle_install(self):
369 p = self.create_pakfire()
370 p.install(self.args.package, ignore_recommended=self.args.without_recommends)
371
372 def handle_reinstall(self):
373 p = self.create_pakfire()
374 p.reinstall(self.args.package)
375
376 def handle_remove(self):
377 p = self.create_pakfire()
378 p.remove(self.args.package)
379
380 def handle_provides(self, long=False):
381 p = self.create_pakfire()
382
383 for pkg in p.provides(self.args.pattern):
384 print pkg.dump(long=long)
385
386 def handle_grouplist(self):
387 p = self.create_pakfire()
388
389 for pkg in p.grouplist(self.args.group[0]):
390 print " * %s" % pkg
391
392 def handle_groupinstall(self):
393 p = self.create_pakfire()
394 p.groupinstall(self.args.group[0])
395
396 def handle_repolist(self):
397 p = self.create_pakfire()
398
399 # Get a list of all repositories.
400 repos = p.repo_list()
401
402 FORMAT = " %-20s %8s %12s %12s "
403 title = FORMAT % (_("Repository"), _("Enabled"), _("Priority"), _("Packages"))
404 print title
405 print "=" * len(title) # spacing line
406
407 for repo in repos:
408 print FORMAT % (repo.name, repo.enabled, repo.priority, len(repo))
409
410 def handle_clean_all(self):
411 print _("Cleaning up everything...")
412
413 p = self.create_pakfire()
414 p.clean_all()
415
416 def handle_check(self):
417 p = self.create_pakfire()
418 p.check()
419
420 def handle_resolvdep(self):
421 p = self.create_pakfire()
422
423 (pkg,) = self.args.package
424
425 solver = p.resolvdep(pkg)
426 assert solver.status
427
428 t = transaction.Transaction.from_solver(p, solver)
429 t.dump()
430
431 def handle_extract(self):
432 p = self.create_pakfire()
433
434 # Open all packages.
435 pkgs = []
436 for pkg in self.args.package:
437 pkg = packages.open(self, None, pkg)
438 pkgs.append(pkg)
439
440 target_prefix = self.args.target
441
442 # Search for binary packages.
443 binary_packages = any([p.type == "binary" for p in pkgs])
444 source_packages = any([p.type == "source" for p in pkgs])
445
446 if binary_packages and source_packages:
447 raise Error, _("Cannot extract mixed package types")
448
449 if binary_packages and not target_prefix:
450 raise Error, _("You must provide an install directory with --target=...")
451
452 elif source_packages and not target_prefix:
453 target_prefix = "/usr/src/packages/"
454
455 if target_prefix == "/":
456 raise Error, _("Cannot extract to /.")
457
458 for pkg in pkgs:
459 if pkg.type == "binary":
460 target_dir = target_prefix
461 elif pkg.type == "source":
462 target_dir = os.path.join(target_prefix, pkg.friendly_name)
463
464 pkg.extract(message=_("Extracting"), prefix=target_dir)
465
466
467 class CliBuilder(Cli):
468 pakfire = base.PakfireBuilder
469
470 def __init__(self):
471 # Check if we are already running in a pakfire container. In that
472 # case, we cannot start another pakfire-builder.
473 if os.environ.get("container", None) == "pakfire-builder":
474 raise PakfireContainerError, _("You cannot run pakfire-builder in a pakfire chroot.")
475
476 self.parser = argparse.ArgumentParser(
477 description = _("Pakfire builder command line interface."),
478 )
479
480 self.parse_common_arguments()
481
482 # Add sub-commands.
483 self.sub_commands = self.parser.add_subparsers()
484
485 self.parse_command_build()
486 self.parse_command_dist()
487 self.parse_command_info()
488 self.parse_command_search()
489 self.parse_command_shell()
490 self.parse_command_update()
491 self.parse_command_provides()
492 self.parse_command_grouplist()
493 self.parse_command_repolist()
494 self.parse_command_clean()
495 self.parse_command_resolvdep()
496 self.parse_command_extract()
497
498 # Finally parse all arguments from the command line and save them.
499 self.args = self.parser.parse_args()
500
501 self.action2func = {
502 "build" : self.handle_build,
503 "dist" : self.handle_dist,
504 "update" : self.handle_update,
505 "info" : self.handle_info,
506 "search" : self.handle_search,
507 "shell" : self.handle_shell,
508 "provides" : self.handle_provides,
509 "grouplist" : self.handle_grouplist,
510 "repolist" : self.handle_repolist,
511 "clean_all" : self.handle_clean_all,
512 "resolvdep" : self.handle_resolvdep,
513 "extract" : self.handle_extract,
514 }
515
516 @property
517 def pakfire_args(self):
518 ret = {
519 "arch" : self.args.arch,
520 }
521
522 if hasattr(self.args, "offline") and self.args.offline:
523 ret["downloader"] = {
524 "offline" : self.args.offline,
525 }
526
527 if hasattr(self.args, "distro"):
528 ret["distro_name"] = self.args.distro
529
530 return ret
531
532 def parse_common_arguments(self, *args, **kwargs):
533 Cli.parse_common_arguments(self, *args, **kwargs)
534
535 self.parser.add_argument("--distro", nargs="?",
536 help=_("Choose the distribution configuration to use for build"))
537
538 self.parser.add_argument("--arch", "-a", nargs="?",
539 help=_("Run pakfire for the given architecture."))
540
541 def parse_command_update(self):
542 # Implement the "update" command.
543 sub_update = self.sub_commands.add_parser("update",
544 help=_("Update the package indexes."))
545 sub_update.add_argument("action", action="store_const", const="update")
546
547 def parse_command_build(self):
548 # Implement the "build" command.
549 sub_build = self.sub_commands.add_parser("build",
550 help=_("Build one or more packages."))
551 sub_build.add_argument("package", nargs=1,
552 help=_("Give name of at least one package to build."))
553 sub_build.add_argument("action", action="store_const", const="build")
554
555 sub_build.add_argument("--resultdir", nargs="?",
556 help=_("Path were the output files should be copied to."))
557 sub_build.add_argument("-m", "--mode", nargs="?", default="development",
558 help=_("Mode to run in. Is either 'release' or 'development' (default)."))
559 sub_build.add_argument("--after-shell", action="store_true",
560 help=_("Run a shell after a successful build."))
561 sub_build.add_argument("--no-install-test", action="store_true",
562 help=_("Do not perform the install test."))
563 sub_build.add_argument("--private-network", action="store_true",
564 help=_("Disable network in container."))
565
566 def parse_command_shell(self):
567 # Implement the "shell" command.
568 sub_shell = self.sub_commands.add_parser("shell",
569 help=_("Go into a shell."))
570 sub_shell.add_argument("package", nargs="?",
571 help=_("Give name of a package."))
572 sub_shell.add_argument("action", action="store_const", const="shell")
573
574 sub_shell.add_argument("-m", "--mode", nargs="?", default="development",
575 help=_("Mode to run in. Is either 'release' or 'development' (default)."))
576 sub_shell.add_argument("--private-network", action="store_true",
577 help=_("Disable network in container."))
578
579 def parse_command_dist(self):
580 # Implement the "dist" command.
581 sub_dist = self.sub_commands.add_parser("dist",
582 help=_("Generate a source package."))
583 sub_dist.add_argument("package", nargs="+",
584 help=_("Give name(s) of a package(s)."))
585 sub_dist.add_argument("action", action="store_const", const="dist")
586
587 sub_dist.add_argument("--resultdir", nargs="?",
588 help=_("Path were the output files should be copied to."))
589
590 def handle_info(self):
591 Cli.handle_info(self, long=True)
592
593 def handle_build(self):
594 # Get the package descriptor from the command line options
595 pkg = self.args.package[0]
596
597 # Check, if we got a regular file
598 if os.path.exists(pkg):
599 pkg = os.path.abspath(pkg)
600
601 else:
602 raise FileNotFoundError, pkg
603
604 # Build argument list.
605 kwargs = {
606 "after_shell" : self.args.after_shell,
607 # Check whether to enable the install test.
608 "install_test" : not self.args.no_install_test,
609 "result_dir" : [self.args.resultdir,],
610 "shell" : True,
611 }
612
613 if self.args.mode == "release":
614 kwargs["release_build"] = True
615 else:
616 kwargs["release_build"] = False
617
618 if self.args.private_network:
619 kwargs["private_network"] = True
620
621 p = self.create_pakfire()
622 p.build(pkg, **kwargs)
623
624 def handle_shell(self):
625 pkg = None
626
627 # Get the package descriptor from the command line options
628 if self.args.package:
629 pkg = self.args.package
630
631 # Check, if we got a regular file
632 if os.path.exists(pkg):
633 pkg = os.path.abspath(pkg)
634
635 else:
636 raise FileNotFoundError, pkg
637
638 if self.args.mode == "release":
639 release_build = True
640 else:
641 release_build = False
642
643 p = self.create_pakfire()
644
645 kwargs = {
646 "release_build" : release_build,
647 }
648
649 # Private network
650 if self.args.private_network:
651 kwargs["private_network"] = True
652
653 p.shell(pkg, **kwargs)
654
655 def handle_dist(self):
656 # Get the packages from the command line options
657 pkgs = []
658
659 for pkg in self.args.package:
660 # Check, if we got a regular file
661 if os.path.exists(pkg):
662 pkg = os.path.abspath(pkg)
663 pkgs.append(pkg)
664
665 else:
666 raise FileNotFoundError, pkg
667
668 # Put packages to where the user said or our
669 # current working directory.
670 resultdir = self.args.resultdir or os.getcwd()
671
672 p = self.create_pakfire()
673 for pkg in pkgs:
674 p.dist(pkg, resultdir=resultdir)
675
676 def handle_provides(self):
677 Cli.handle_provides(self, long=True)
678
679
680 class CliServer(Cli):
681 pakfire = base.PakfireServer
682
683 def __init__(self):
684 self.parser = argparse.ArgumentParser(
685 description = _("Pakfire server command line interface."),
686 )
687
688 self.parse_common_arguments()
689
690 # Add sub-commands.
691 self.sub_commands = self.parser.add_subparsers()
692
693 self.parse_command_build()
694 self.parse_command_keepalive()
695 self.parse_command_repoupdate()
696 self.parse_command_repo()
697 self.parse_command_info()
698
699 # Finally parse all arguments from the command line and save them.
700 self.args = self.parser.parse_args()
701
702 #self.server = server.Server(**self.pakfire_args)
703
704 self.action2func = {
705 "build" : self.handle_build,
706 "info" : self.handle_info,
707 "keepalive" : self.handle_keepalive,
708 "repoupdate" : self.handle_repoupdate,
709 "repo_create": self.handle_repo_create,
710 }
711
712 @property
713 def pakfire_args(self):
714 ret = {}
715
716 if hasattr(self.args, "offline") and self.args.offline:
717 ret["downloader"] = {
718 "offline" : self.args.offline,
719 }
720
721 return ret
722
723 def parse_command_build(self):
724 # Implement the "build" command.
725 sub_build = self.sub_commands.add_parser("build",
726 help=_("Send a scrach build job to the server."))
727 sub_build.add_argument("package", nargs=1,
728 help=_("Give name of at least one package to build."))
729 sub_build.add_argument("--arch", "-a",
730 help=_("Limit build to only these architecture(s)."))
731 sub_build.add_argument("action", action="store_const", const="build")
732
733 def parse_command_keepalive(self):
734 # Implement the "keepalive" command.
735 sub_keepalive = self.sub_commands.add_parser("keepalive",
736 help=_("Send a keepalive to the server."))
737 sub_keepalive.add_argument("action", action="store_const",
738 const="keepalive")
739
740 def parse_command_repoupdate(self):
741 # Implement the "repoupdate" command.
742 sub_repoupdate = self.sub_commands.add_parser("repoupdate",
743 help=_("Update all repositories."))
744 sub_repoupdate.add_argument("action", action="store_const",
745 const="repoupdate")
746
747 def parse_command_repo(self):
748 sub_repo = self.sub_commands.add_parser("repo",
749 help=_("Repository management commands."))
750
751 sub_repo_commands = sub_repo.add_subparsers()
752
753 self.parse_command_repo_create(sub_repo_commands)
754
755 def parse_command_repo_create(self, sub_commands):
756 sub_create = sub_commands.add_parser("create",
757 help=_("Create a new repository index."))
758 sub_create.add_argument("path", nargs=1,
759 help=_("Path to the packages."))
760 sub_create.add_argument("inputs", nargs="+",
761 help=_("Path to input packages."))
762 sub_create.add_argument("--key", "-k", nargs="?",
763 help=_("Key to sign the repository with."))
764 sub_create.add_argument("action", action="store_const", const="repo_create")
765
766 def parse_command_info(self):
767 sub_info = self.sub_commands.add_parser("info",
768 help=_("Dump some information about this machine."))
769 sub_info.add_argument("action", action="store_const", const="info")
770
771 def handle_keepalive(self):
772 self.server.update_info()
773
774 def handle_build(self):
775 # Arch.
776 if self.args.arch:
777 arches = self.args.arch.split()
778
779 (package,) = self.args.package
780
781 self.server.create_scratch_build({})
782 return
783
784 # Temporary folter for source package.
785 tmpdir = "/tmp/pakfire-%s" % util.random_string()
786
787 try:
788 os.makedirs(tmpdir)
789
790 pakfire.dist(package, resultdir=[tmpdir,])
791
792 for file in os.listdir(tmpdir):
793 file = os.path.join(tmpdir, file)
794
795 print file
796
797 finally:
798 if os.path.exists(tmpdir):
799 util.rm(tmpdir)
800
801 def handle_repoupdate(self):
802 self.server.update_repositories()
803
804 def handle_repo_create(self):
805 path = self.args.path[0]
806
807 p = self.create_pakfire()
808 p.repo_create(path, self.args.inputs, key_id=self.args.key)
809
810 def handle_info(self):
811 info = self.server.info()
812
813 print "\n".join(info)
814
815
816 class CliBuilderIntern(Cli):
817 def __init__(self):
818 self.parser = argparse.ArgumentParser(
819 description = _("Pakfire builder command line interface."),
820 )
821
822 self.parse_common_arguments()
823
824 # Add sub-commands.
825 self.sub_commands = self.parser.add_subparsers()
826
827 self.parse_command_build()
828
829 # Finally parse all arguments from the command line and save them.
830 self.args = self.parser.parse_args()
831
832 self.action2func = {
833 "build" : self.handle_build,
834 }
835
836 def parse_command_build(self):
837 # Implement the "build" command.
838 sub_build = self.sub_commands.add_parser("build",
839 help=_("Build one or more packages."))
840 sub_build.add_argument("package", nargs=1,
841 help=_("Give name of at least one package to build."))
842 sub_build.add_argument("action", action="store_const", const="build")
843
844 sub_build.add_argument("-a", "--arch",
845 help=_("Build the package for the given architecture."))
846 sub_build.add_argument("--resultdir", nargs="?",
847 help=_("Path were the output files should be copied to."))
848 sub_build.add_argument("-m", "--mode", nargs="?", default="development",
849 help=_("Mode to run in. Is either 'release' or 'development' (default)."))
850 sub_build.add_argument("--nodeps", action="store_true",
851 help=_("Do not verify build dependencies."))
852 sub_build.add_argument("--prepare", action="store_true",
853 help=_("Only run the prepare stage."))
854
855 def handle_build(self):
856 # Get the package descriptor from the command line options
857 pkg = self.args.package[0]
858
859 # Check, if we got a regular file
860 if os.path.exists(pkg):
861 pkg = os.path.abspath(pkg)
862 else:
863 raise FileNotFoundError, pkg
864
865 # Create pakfire instance.
866 c = config.ConfigBuilder()
867 p = base.Pakfire(arch = self.args.arch, config = c)
868
869 # Disable all repositories.
870 if self.args.nodeps:
871 p.repos.disable_repo("*")
872
873 # Limit stages that are to be run.
874 if self.args.prepare:
875 stages = ["prepare"]
876 else:
877 stages = None
878
879 p.build(pkg, resultdir=self.args.resultdir, stages=stages)
880
881
882 class CliClient(Cli):
883 pakfire = base.PakfireClient
884
885 def __init__(self):
886 self.parser = argparse.ArgumentParser(
887 description = _("Pakfire client command line interface."),
888 )
889
890 self.parse_common_arguments(offline_switch=True)
891
892 # Add sub-commands.
893 self.sub_commands = self.parser.add_subparsers()
894
895 self.parse_command_build()
896 self.parse_command_connection_check()
897 self.parse_command_info()
898 self.parse_command_jobs()
899 self.parse_command_builds()
900 self.parse_command_test()
901
902 # Finally parse all arguments from the command line and save them.
903 self.args = self.parser.parse_args()
904
905 self.action2func = {
906 "build" : self.handle_build,
907 "conn-check" : self.handle_connection_check,
908 "info" : self.handle_info,
909 "jobs_show" : self.handle_jobs_show,
910 "jobs_active" : self.handle_jobs_active,
911 "jobs_latest" : self.handle_jobs_latest,
912 "builds_show" : self.handle_builds_show,
913 "test" : self.handle_test,
914 }
915
916 # Read configuration.
917 self.config = config.ConfigClient()
918
919 # Create connection to pakfire hub.
920 self.client = client.PakfireClient(self.config)
921
922 @property
923 def pakfire_args(self):
924 return {
925 "config" : self.config,
926 }
927
928 def parse_command_build(self):
929 # Parse "build" command.
930 sub_build = self.sub_commands.add_parser("build",
931 help=_("Build a package remotely."))
932 sub_build.add_argument("package", nargs=1,
933 help=_("Give name of a package to build."))
934 sub_build.add_argument("action", action="store_const", const="build")
935
936 sub_build.add_argument("-a", "--arch",
937 help=_("Build the package for the given architecture."))
938
939 def parse_command_info(self):
940 # Implement the "info" command.
941 sub_info = self.sub_commands.add_parser("info",
942 help=_("Print some information about this host."))
943 sub_info.add_argument("action", action="store_const", const="info")
944
945 def parse_command_connection_check(self):
946 # Implement the "conn-check" command.
947 sub_conn_check = self.sub_commands.add_parser("conn-check",
948 help=_("Check the connection to the hub."))
949 sub_conn_check.add_argument("action", action="store_const", const="conn-check")
950
951 def parse_command_jobs(self):
952 sub_jobs = self.sub_commands.add_parser("jobs",
953 help=_("Show information about build jobs."))
954
955 sub_jobs_commands = sub_jobs.add_subparsers()
956
957 self.parse_command_jobs_active(sub_jobs_commands)
958 self.parse_command_jobs_latest(sub_jobs_commands)
959 self.parse_command_jobs_show(sub_jobs_commands)
960
961 def parse_command_jobs_active(self, sub_commands):
962 sub_active = sub_commands.add_parser("active",
963 help=_("Show a list of all active jobs."))
964 sub_active.add_argument("action", action="store_const", const="jobs_active")
965
966 def parse_command_jobs_latest(self, sub_commands):
967 sub_latest = sub_commands.add_parser("latest",
968 help=_("Show a list of all recently finished of failed build jobs."))
969 sub_latest.add_argument("action", action="store_const", const="jobs_latest")
970
971 def parse_command_jobs_show(self, sub_commands):
972 sub_show = sub_commands.add_parser("show",
973 help=_("Show details about given build job."))
974 sub_show.add_argument("job_id", nargs=1, help=_("The ID of the build job."))
975 sub_show.add_argument("action", action="store_const", const="jobs_show")
976
977 def parse_command_builds(self):
978 sub_builds = self.sub_commands.add_parser("builds",
979 help=_("Show information about builds."))
980
981 sub_builds_commands = sub_builds.add_subparsers()
982
983 self.parse_command_builds_show(sub_builds_commands)
984
985 def parse_command_builds_show(self, sub_commands):
986 sub_show = sub_commands.add_parser("show",
987 help=_("Show details about the given build."))
988 sub_show.add_argument("build_id", nargs=1, help=_("The ID of the build."))
989 sub_show.add_argument("action", action="store_const", const="builds_show")
990
991 def parse_command_test(self):
992 sub_test = self.sub_commands.add_parser("test",
993 help=_("Test the connection to the hub."))
994 sub_test.add_argument("error_code", nargs=1, help=_("Error code to test."))
995 sub_test.add_argument("action", action="store_const", const="test")
996
997 def handle_build(self):
998 (package,) = self.args.package
999
1000 # XXX just for now, we do only upload source pfm files.
1001 assert os.path.exists(package)
1002
1003 # Create a temporary directory.
1004 temp_dir = tempfile.mkdtemp()
1005
1006 try:
1007 if package.endswith(".%s" % MAKEFILE_EXTENSION):
1008 pakfire_args = {}
1009
1010 # Create a source package from the makefile.
1011 p = self.pakfire(**self.pakfire_args)
1012 package = p.dist(package, temp_dir)
1013
1014 elif package.endswith(".%s" % PACKAGE_EXTENSION):
1015 pass
1016
1017 else:
1018 raise Exception, "Unknown filetype: %s" % package
1019
1020 # Format arches.
1021 if self.args.arch:
1022 arches = self.args.arch.split(",")
1023 else:
1024 arches = None
1025
1026 # Create a new build on the server.
1027 build_id = self.client.build_create(package, build_type="scratch",
1028 arches=arches)
1029
1030 finally:
1031 # Cleanup the temporary directory and all files.
1032 if os.path.exists(temp_dir):
1033 shutil.rmtree(temp_dir, ignore_errors=True)
1034
1035 # Monitor the build.
1036 if build_id:
1037 self.watch_build(build_id)
1038
1039 def handle_info(self):
1040 ret = []
1041
1042 ret.append("")
1043 ret.append(" PAKFIRE %s" % PAKFIRE_VERSION)
1044 ret.append("")
1045 ret.append(" %-20s: %s" % (_("Hostname"), system.hostname))
1046 ret.append(" %-20s: %s" % (_("Pakfire hub"), self.config.get("client", "server")))
1047 if self.config.get("client", "username") and self.config.get("client", "password"):
1048 ret.append(" %-20s: %s" % \
1049 (_("Username"), self.config.get("client", "username")))
1050 ret.append("")
1051
1052 # Hardware information
1053 ret.append(" %s:" % _("Hardware information"))
1054 ret.append(" %-16s: %s" % (_("CPU model"), system.cpu_model))
1055 ret.append(" %-16s: %s" % (_("Memory"), util.format_size(system.memory)))
1056 ret.append(" %-16s: %s" % (_("Parallelism"), system.parallelism))
1057 ret.append("")
1058 ret.append(" %-16s: %s" % (_("Native arch"), system.native_arch))
1059 if not system.arch == system.native_arch:
1060 ret.append(" %-16s: %s" % (_("Default arch"), system.arch))
1061
1062 header = _("Supported arches")
1063 for arch in system.supported_arches:
1064 ret.append(" %-16s: %s" % (header, arch))
1065 header = ""
1066 ret.append("")
1067
1068 for line in ret:
1069 print line
1070
1071 def handle_connection_check(self):
1072 ret = []
1073
1074 address = self.client.get_my_address()
1075 ret.append(" %-20s: %s" % (_("Your IP address"), address))
1076 ret.append("")
1077
1078 authenticated = self.client.check_auth()
1079 if authenticated:
1080 ret.append(" %s" % _("You are authenticated to the build service:"))
1081
1082 user = self.client.get_user_profile()
1083 assert user, "Could not fetch user infomation"
1084
1085 keys = [
1086 ("name", _("User name")),
1087 ("realname", _("Real name")),
1088 ("email", _("Email address")),
1089 ("registered", _("Registered")),
1090 ]
1091
1092 for key, desc in keys:
1093 ret.append(" %-18s: %s" % (desc, user.get(key)))
1094
1095 else:
1096 ret.append(_("You could not be authenticated to the build service."))
1097
1098 for line in ret:
1099 print line
1100
1101 def _print_jobs(self, jobs, heading=None):
1102 if heading:
1103 print "%s:" % heading
1104 print
1105
1106 for job in jobs:
1107 line = " [%(type)8s] %(name)-30s: %(state)s"
1108
1109 print line % job
1110
1111 print # Empty line at the end.
1112
1113 def handle_jobs_active(self):
1114 jobs = self.client.get_active_jobs()
1115
1116 if not jobs:
1117 print _("No ongoing jobs found.")
1118 return
1119
1120 self._print_jobs(jobs, _("Active build jobs"))
1121
1122 def handle_jobs_latest(self):
1123 jobs = self.client.get_latest_jobs()
1124
1125 if not jobs:
1126 print _("No jobs found.")
1127 return
1128
1129 self._print_jobs(jobs, _("Recently processed build jobs"))
1130
1131 def handle_builds_show(self):
1132 (build_id,) = self.args.build_id
1133
1134 build = self.client.get_build(build_id)
1135 if not build:
1136 print _("A build with ID %s could not be found.") % build_id
1137 return
1138
1139 print _("Build: %(name)s") % build
1140
1141 fmt = "%-14s: %s"
1142 lines = [
1143 fmt % (_("State"), build["state"]),
1144 fmt % (_("Priority"), build["priority"]),
1145 ]
1146
1147 lines.append("%s:" % _("Jobs"))
1148 for job in build["jobs"]:
1149 lines.append(" * [%(uuid)s] %(name)-30s: %(state)s" % job)
1150
1151 for line in lines:
1152 print " ", line
1153 print
1154
1155 def handle_jobs_show(self):
1156 (job_id,) = self.args.job_id
1157
1158 job = self.client.get_job(job_id)
1159 if not job:
1160 print _("A job with ID %s could not be found.") % job_id
1161 return
1162
1163 builder = None
1164 if job["builder_id"]:
1165 builder = self.client.get_builder(job["builder_id"])
1166
1167 print _("Job: %(name)s") % job
1168
1169 fmt = "%-14s: %s"
1170 lines = [
1171 fmt % (_("State"), job["state"]),
1172 fmt % (_("Arch"), job["arch"]),
1173 ]
1174
1175 if builder:
1176 lines += [
1177 fmt % (_("Build host"), builder["name"]),
1178 "",
1179 ]
1180
1181 lines += [
1182 fmt % (_("Time created"), job["time_created"]),
1183 fmt % (_("Time started"), job["time_started"]),
1184 fmt % (_("Time finished"), job["time_finished"]),
1185 fmt % (_("Duration"), job["duration"]),
1186 ]
1187
1188 if job["packages"]:
1189 lines += ["", "%s:" % _("Packages")]
1190
1191 for pkg in job["packages"]:
1192 pkg_lines = [
1193 "* %(friendly_name)s" % pkg,
1194 " %(uuid)s" % pkg,
1195 "",
1196 ]
1197
1198 lines += [" %s" % line for line in pkg_lines]
1199
1200 for line in lines:
1201 print " ", line
1202 print # New line.
1203
1204 def handle_test(self):
1205 error_code = self.args.error_code[0]
1206
1207 try:
1208 error_code = int(error_code)
1209 except ValueError:
1210 error_code = 0
1211
1212 if error_code < 100 or error_code > 999:
1213 raise Error, _("Invalid error code given.")
1214
1215 res = self.client.test_code(error_code)
1216 print _("Reponse from the server: %s") % res
1217
1218 def watch_build(self, build_id):
1219 print self.client.build_get(build_id)
1220 # XXX TODO
1221 print build_id
1222
1223
1224 class CliDaemon(Cli):
1225 def __init__(self):
1226 self.parser = argparse.ArgumentParser(
1227 description = _("Pakfire daemon command line interface."),
1228 )
1229
1230 self.parse_common_arguments(offline_switch=True)
1231
1232 # Finally parse all arguments from the command line and save them.
1233 self.args = self.parser.parse_args()
1234
1235 def run(self):
1236 """
1237 Runs the pakfire daemon with provided settings.
1238 """
1239 # Read the configuration file for the daemon.
1240 self.config = config.ConfigDaemon()
1241 logger.setup_logging(self.config)
1242
1243 # Create daemon instance.
1244 d = daemon.PakfireDaemon(self.config)
1245 try:
1246 d.run()
1247
1248 # We cannot just kill the daemon, it needs a smooth shutdown.
1249 except (SystemExit, KeyboardInterrupt):
1250 d.shutdown()
1251
1252
1253 class CliKey(Cli):
1254 pakfire = base.PakfireKey
1255
1256 def __init__(self):
1257 self.parser = argparse.ArgumentParser(
1258 description = _("Pakfire key command line interface."),
1259 )
1260
1261 self.parse_common_arguments(offline_switch=True)
1262
1263 # Add sub-commands.
1264 self.sub_commands = self.parser.add_subparsers()
1265
1266 self.parse_command_generate()
1267 self.parse_command_import()
1268 self.parse_command_export()
1269 self.parse_command_delete()
1270 self.parse_command_list()
1271 self.parse_command_sign()
1272 self.parse_command_verify()
1273
1274 # Finally parse all arguments from the command line and save them.
1275 self.args = self.parser.parse_args()
1276
1277 self.action2func = {
1278 "generate" : self.handle_generate,
1279 "import" : self.handle_import,
1280 "export" : self.handle_export,
1281 "delete" : self.handle_delete,
1282 "list" : self.handle_list,
1283 "sign" : self.handle_sign,
1284 "verify" : self.handle_verify,
1285 }
1286
1287 @property
1288 def pakfire_args(self):
1289 return {}
1290
1291 def parse_command_generate(self):
1292 # Parse "generate" command.
1293 sub_gen = self.sub_commands.add_parser("generate",
1294 help=_("Import a key from file."))
1295 sub_gen.add_argument("--realname", nargs=1,
1296 help=_("The real name of the owner of this key."))
1297 sub_gen.add_argument("--email", nargs=1,
1298 help=_("The email address of the owner of this key."))
1299 sub_gen.add_argument("action", action="store_const", const="generate")
1300
1301 def parse_command_import(self):
1302 # Parse "import" command.
1303 sub_import = self.sub_commands.add_parser("import",
1304 help=_("Import a key from file."))
1305 sub_import.add_argument("filename", nargs=1,
1306 help=_("Filename of that key to import."))
1307 sub_import.add_argument("action", action="store_const", const="import")
1308
1309 def parse_command_export(self):
1310 # Parse "export" command.
1311 sub_export = self.sub_commands.add_parser("export",
1312 help=_("Export a key to a file."))
1313 sub_export.add_argument("keyid", nargs=1,
1314 help=_("The ID of the key to export."))
1315 sub_export.add_argument("filename", nargs=1,
1316 help=_("Write the key to this file."))
1317 sub_export.add_argument("action", action="store_const", const="export")
1318
1319 def parse_command_delete(self):
1320 # Parse "delete" command.
1321 sub_del = self.sub_commands.add_parser("delete",
1322 help=_("Delete a key from the local keyring."))
1323 sub_del.add_argument("keyid", nargs=1,
1324 help=_("The ID of the key to delete."))
1325 sub_del.add_argument("action", action="store_const", const="delete")
1326
1327 def parse_command_list(self):
1328 # Parse "list" command.
1329 sub_list = self.sub_commands.add_parser("list",
1330 help=_("List all imported keys."))
1331 sub_list.add_argument("action", action="store_const", const="list")
1332
1333 def parse_command_sign(self):
1334 # Implement the "sign" command.
1335 sub_sign = self.sub_commands.add_parser("sign",
1336 help=_("Sign one or more packages."))
1337 sub_sign.add_argument("--key", "-k", nargs=1,
1338 help=_("Key that is used sign the package(s)."))
1339 sub_sign.add_argument("package", nargs="+",
1340 help=_("Package(s) to sign."))
1341 sub_sign.add_argument("action", action="store_const", const="sign")
1342
1343 def parse_command_verify(self):
1344 # Implement the "verify" command.
1345 sub_verify = self.sub_commands.add_parser("verify",
1346 help=_("Verify one or more packages."))
1347 #sub_verify.add_argument("--key", "-k", nargs=1,
1348 # help=_("Key that is used verify the package(s)."))
1349 sub_verify.add_argument("package", nargs="+",
1350 help=_("Package(s) to verify."))
1351 sub_verify.add_argument("action", action="store_const", const="verify")
1352
1353 def handle_generate(self):
1354 realname = self.args.realname[0]
1355 email = self.args.email[0]
1356
1357 print _("Generating the key may take a moment...")
1358 print
1359
1360 # Generate the key.
1361 p = self.create_pakfire()
1362 p.keyring.gen_key(realname, email)
1363
1364 def handle_import(self):
1365 filename = self.args.filename[0]
1366
1367 # Simply import the file.
1368 p = self.create_pakfire()
1369 p.keyring.import_key(filename)
1370
1371 def handle_export(self):
1372 keyid = self.args.keyid[0]
1373 filename = self.args.filename[0]
1374
1375 p = self.create_pakfire()
1376 p.keyring.export_key(keyid, filename)
1377
1378 def handle_delete(self):
1379 keyid = self.args.keyid[0]
1380
1381 p = self.create_pakfire()
1382 p.keyring.delete_key(keyid)
1383
1384 def handle_list(self):
1385 p = self.create_pakfire()
1386 for line in p.keyring.list_keys():
1387 print line
1388
1389 def handle_sign(self):
1390 # Get the files from the command line options
1391 files = []
1392
1393 for file in self.args.package:
1394 # Check, if we got a regular file
1395 if os.path.exists(file):
1396 file = os.path.abspath(file)
1397 files.append(file)
1398
1399 else:
1400 raise FileNotFoundError, file
1401
1402 key = self.args.key[0]
1403
1404 # Create pakfire instance.
1405 p = self.create_pakfire()
1406
1407 for file in files:
1408 # Open the package.
1409 pkg = packages.open(p, None, file)
1410
1411 print _("Signing %s...") % pkg.friendly_name
1412 pkg.sign(key)
1413
1414 def handle_verify(self):
1415 # Get the files from the command line options
1416 files = []
1417
1418 for file in self.args.package:
1419 # Check, if we got a regular file
1420 if os.path.exists(file) and not os.path.isdir(file):
1421 file = os.path.abspath(file)
1422 files.append(file)
1423
1424 # Create pakfire instance.
1425 p = self.create_pakfire()
1426
1427 for file in files:
1428 # Open the package.
1429 pkg = packages.open(p, None, file)
1430
1431 print _("Verifying %s...") % pkg.friendly_name
1432 sigs = pkg.verify()
1433
1434 for sig in sigs:
1435 key = p.keyring.get_key(sig.fpr)
1436 if key:
1437 subkey = key.subkeys[0]
1438
1439 print " %s %s" % (subkey.fpr[-16:], key.uids[0].uid)
1440 if sig.validity:
1441 print " %s" % _("This signature is valid.")
1442
1443 else:
1444 print " %s <%s>" % (sig.fpr, _("Unknown key"))
1445 print " %s" % _("Could not check if this signature is valid.")
1446
1447 created = datetime.datetime.fromtimestamp(sig.timestamp)
1448 print " %s" % _("Created: %s") % created
1449
1450 if sig.exp_timestamp:
1451 expires = datetime.datetime.fromtimestamp(sig.exp_timestamp)
1452 print " %s" % _("Expires: %s") % expires
1453
1454 print # Empty line