]> git.ipfire.org Git - people/stevee/pakfire.git/blame - src/pakfire/cli.py
transaction: Add a simple confirmation before proceeding
[people/stevee/pakfire.git] / src / pakfire / cli.py
CommitLineData
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
22import argparse
68c0e769 23import datetime
17563cd0 24import logging
936f6b37 25import os
b913e798 26import shutil
17563cd0 27import signal
47a4cb89 28import sys
b913e798 29import tempfile
10b3cc9c 30import time
47a4cb89 31
964aa579 32from . import base
2274aab3 33from . import builder
964aa579
MT
34from . import client
35from . import config
36from . import daemon
80ed5fae 37from . import ui
964aa579
MT
38from . import util
39
40from .system import system
41from .constants import *
42from .i18n import _
47a4cb89 43
47a4cb89 44class Cli(object):
e75d1e3c
MT
45 default_path = "/"
46
47a4cb89 47 def __init__(self):
80ed5fae 48 self.ui = ui.cli.CliUI()
685cb819 49
67b139fb
MT
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
5f4c668a
MT
59 parser.add_argument("--arch", "-a", nargs="?",
60 help=_("Run pakfire for the given architecture"))
67b139fb
MT
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"))
47857184
MT
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"))
67b139fb
MT
78 check_update.add_argument("--allow-vendorchange", action="store_true",
79 help=_("Allow changing the vendor of packages"))
67b139fb
MT
80
81 # clean
82 clean = subparsers.add_parser("clean", help=_("Cleanup all temporary files"))
83 clean.set_defaults(func=self.handle_clean)
84
67b139fb
MT
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
8d86a58f
MT
95 # execute
96 execute = subparsers.add_parser("execute",
97 help=_("Executes a command in the pakfire environment (useful for development)"))
1577db9b 98 execute.add_argument("--bind", action="append", default=[], dest="binds",
8d86a58f
MT
99 help=_("Bind-mounts the given directory"))
100 execute.add_argument("command", nargs=argparse.REMAINDER)
101 execute.set_defaults(func=self.handle_execute)
102
67b139fb
MT
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)"))
31ed921f
MT
115 info.add_argument("--long", action="store_true",
116 help=_("Show more information"))
67b139fb
MT
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
67b139fb
MT
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
7b6f9f7b
MT
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
67b139fb
MT
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="+",
47857184
MT
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"))
67b139fb 176 update.add_argument("--allow-vendorchange", action="store_true",
47857184 177 help=_("Allow changing the vendor of packages"))
67b139fb 178 update.set_defaults(func=self.handle_update)
685cb819 179
67b139fb 180 return parser.parse_args()
685cb819 181
95a7e4d9
MT
182 def _add_common_arguments(self, parser, offline_switch=True):
183 parser.add_argument("--version", action="version",
50381f5c
MT
184 version="%(prog)s " + PAKFIRE_VERSION)
185
95a7e4d9 186 parser.add_argument("-v", "--verbose", action="store_true",
47a4cb89
MT
187 help=_("Enable verbose output."))
188
95a7e4d9 189 parser.add_argument("-c", "--config", nargs="?",
47a4cb89
MT
190 help=_("Path to a configuration file to load."))
191
95a7e4d9 192 parser.add_argument("--disable-repo", nargs="*", metavar="REPO",
685cb819 193 help=_("Disable a repository temporarily."), default=[])
f781b1ab 194
95a7e4d9 195 parser.add_argument("--enable-repo", nargs="*", metavar="REPO",
685cb819 196 help=_("Enable a repository temporarily."), default=[])
f9a012a8 197
c62d93f1 198 if offline_switch:
95a7e4d9 199 parser.add_argument("--offline", action="store_true",
c62d93f1 200 help=_("Run pakfire in offline mode."))
6a509182 201
67b139fb 202 def pakfire(self, ns):
e75d1e3c 203 p = base.Pakfire(
5f4c668a 204 arch=ns.arch,
8a36f1f2 205 conf=ns.config,
e75d1e3c
MT
206 path=ns.root if "root" in ns else self.default_path,
207 offline=ns.offline,
208 )
05fb1da0 209
67b139fb 210 # Disable repositories.
8ef6c388
MT
211 for repo_name in ns.disable_repo:
212 repo = p.get_repo(repo_name)
213 repo.enabled = False
67b139fb
MT
214
215 # Enable repositories.
8ef6c388
MT
216 for repo_name in ns.enable_repo:
217 repo = p.get_repo(repo_name)
218 repo.enabled = True
67b139fb
MT
219
220 return p
995ed2dd 221
8d86a58f
MT
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
59b9d1c2
MT
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)
8d86a58f 237
47a4cb89 238 def run(self):
95a7e4d9 239 args = self.parse_cli()
74b61c5c 240 assert args.func, "Argument function not defined"
47a4cb89 241
17563cd0
MT
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
27160734
MT
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
17563cd0
MT
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
47a4cb89 279
47857184
MT
280 def _dump_transaction(self, transaction):
281 """
282 Dumps the transaction
283 """
b546c786 284 t = transaction.dump()
47857184 285
b546c786
MT
286 for line in t.splitlines():
287 self.ui.message(line)
288
47857184 289 def _execute_transaction(self, transaction):
dea70c9f 290 transaction.run()
b546c786 291
145f61fe 292 def handle_info(self, ns):
279509bc
MT
293 with self.pakfire(ns) as p:
294 for pkg in p.info(ns.package):
31ed921f 295 s = pkg.dump(long=ns.long)
312fd26f 296 self.ui.message(s)
47a4cb89 297
279509bc
MT
298 def handle_search(self, ns):
299 with self.pakfire(ns) as p:
300 for pkg in p.search(ns.pattern):
2b2d5b1b
MT
301 # Skip any -debuginfo packages
302 if pkg.name.endswith("-debuginfo"):
303 continue
304
465dc959 305 self.ui.message("%-24s: %s" % (pkg.name, pkg.summary))
47a4cb89 306
47857184
MT
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 )
d314d72b 314
47857184
MT
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
d314d72b 320
47857184
MT
321 # Otherwise we execute the transaction
322 self._execute_transaction(transaction)
d314d72b 323
7b6f9f7b 324 def handle_sync(self, ns):
47857184
MT
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)
279509bc
MT
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:
b546c786
MT
347 transaction = p.install(ns.package, without_recommends=ns.without_recommends)
348
349 # Execute the transaction
350 self._execute_transaction(transaction)
279509bc
MT
351
352 def handle_reinstall(self, ns):
353 with self.pakfire(ns) as p:
b546c786
MT
354 transaction = p.reinstall(ns.package)
355
356 # Execute the transaction
357 self._execute_transaction(transaction)
279509bc
MT
358
359 def handle_remove(self, ns):
360 with self.pakfire(ns) as p:
2f1289eb 361 transaction = p.erase(ns.package)
b546c786
MT
362
363 self._execute_transaction(transaction)
279509bc
MT
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
279509bc
MT
371 def handle_repolist(self, ns):
372 with self.pakfire(ns) as p:
279509bc
MT
373 FORMAT = " %-20s %8s %12s %12s "
374 title = FORMAT % (_("Repository"), _("Enabled"), _("Priority"), _("Packages"))
375 print(title)
376 print("=" * len(title)) # spacing line
377
6e7e70a2 378 for repo in p.repos:
279509bc
MT
379 print(FORMAT % (repo.name, repo.enabled, repo.priority, len(repo)))
380
381 def handle_clean(self, ns):
cb6d631c 382 self.ui.message(_("Cleaning up everything..."))
31267a64 383
cb6d631c
MT
384 p = self.pakfire(ns)
385 p.clean()
c901e1f8 386
279509bc
MT
387 def handle_check(self, ns):
388 with self.pakfire(ns) as p:
f466d0db
MT
389 # This will throw an exception when there are errors
390 transaction = p.check()
391
392 self.ui.message(_("Everything okay"))
36b328f2 393
279509bc
MT
394 def handle_extract(self, ns):
395 with self.pakfire(ns) as p:
e7da5970 396 p.extract(ns.package, target=ns.target)
995ed2dd 397
47a4cb89
MT
398
399class CliBuilder(Cli):
e75d1e3c
MT
400 default_path = None
401
145f61fe
MT
402 def parse_cli(self):
403 parser = argparse.ArgumentParser(
404 description = _("Pakfire builder command line interface"),
47a4cb89 405 )
145f61fe 406 subparsers = parser.add_subparsers()
47a4cb89 407
145f61fe
MT
408 # Add common arguments
409 self._add_common_arguments(parser)
47a4cb89 410
145f61fe
MT
411 # Add additional arguments
412 parser.add_argument("--arch", "-a", nargs="?",
413 help=_("Run pakfire for the given architecture"))
e947f34f 414 parser.add_argument("--distro", nargs="?", default="ipfire3", # XXX for now
145f61fe 415 help=_("Choose the distribution configuration to use for build"))
1c2ff7a3
MT
416 parser.add_argument("--disable-snapshot", action="store_true",
417 help=_("Disable using snapshots"))
47a4cb89 418
145f61fe
MT
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"))
0c547b16 438 clean.set_defaults(func=self.handle_clean)
145f61fe
MT
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)"))
31ed921f
MT
459 info.add_argument("--filelist", action="store_true",
460 help=_("Show filelist"))
145f61fe
MT
461 info.add_argument("package", nargs="+",
462 help=_("Give at least the name of one package."))
31ed921f 463 info.set_defaults(func=self.handle_info)
145f61fe 464
145f61fe
MT
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
145f61fe
MT
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"))
b3cdc86c 484 shell.add_argument("package", nargs="*", help=_("Give name of a package"))
f0ae5ac2
MT
485 shell.add_argument("--install", nargs="*",
486 help=_("Packages that should be installed in the shell"))
145f61fe
MT
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"))
47a4cb89 493
145f61fe
MT
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()
47a4cb89 499
2274aab3 500 def builder(self, ns):
e947f34f
MT
501 # Find distro configuration file
502 conf = os.path.join(CONFIG_DISTRO_DIR, "%s.conf" % ns.distro)
503
1c2ff7a3
MT
504 return builder.Builder(
505 conf=conf,
506 arch=ns.arch,
507 enable_snapshot=not ns.disable_snapshot
508 )
47a4cb89 509
2274aab3
MT
510 def handle_build(self, ns):
511 package, = ns.package
4fffe3c4 512
2274aab3
MT
513 # Initialise a builder instance and build this package
514 with self.builder(ns) as b:
515 b.build(package)
392371f7 516
5cea773c
MT
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):
31ed921f 521 s = pkg.dump(long=True, filelist=ns.filelist)
5cea773c
MT
522 self.ui.message(s)
523
a7c71410
MT
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
2274aab3
MT
535 def handle_shell(self, ns):
536 with self.builder(ns) as b:
b3cdc86c 537 b.shell(packages=ns.package, install=ns.install)
47a4cb89 538
0fcb9c3c 539 def handle_dist(self, ns):
e412b8dc
MT
540 # Get the packages from the command line options
541 pkgs = []
47a4cb89 542
0fcb9c3c 543 for pkg in ns.package:
e412b8dc
MT
544 # Check, if we got a regular file
545 if os.path.exists(pkg):
546 pkg = os.path.abspath(pkg)
7c8f2953 547 pkgs.append(pkg)
47a4cb89 548
e412b8dc 549 else:
964aa579 550 raise FileNotFoundError(pkg)
7c8f2953 551
7d40ac70
MT
552 # Put packages to where the user said or our
553 # current working directory.
0fcb9c3c 554 resultdir = ns.resultdir or os.getcwd()
7d40ac70 555
0fcb9c3c 556 p = self.pakfire(ns)
7d40ac70 557 for pkg in pkgs:
af41db7e 558 p.dist(pkg, resultdir)
47a4cb89 559
47a4cb89 560
c62d93f1
MT
561class CliClient(Cli):
562 def __init__(self):
95a7e4d9
MT
563 # Create connection to pakfire hub
564 self.client = client.Client()
565
566 def parse_cli(self):
567 parser = argparse.ArgumentParser(
755dd8db 568 description = _("Pakfire client command line interface"),
c62d93f1 569 )
95a7e4d9 570 subparsers = parser.add_subparsers(help=_("sub-command help"))
c62d93f1 571
95a7e4d9
MT
572 # Add common arguments
573 self._add_common_arguments(parser, offline_switch=False)
c62d93f1 574
95a7e4d9
MT
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)
c62d93f1 579
95a7e4d9 580 build.add_argument("-a", "--arch",
755dd8db 581 help=_("Build the package(s) for the given architecture only"))
c62d93f1 582
95a7e4d9
MT
583 # check-connection
584 check_connection = subparsers.add_parser("check-connection",
6e3e13fb 585 help=_("Check the connection to the hub"))
95a7e4d9
MT
586 check_connection.set_defaults(func=self.handle_check_connection)
587
ec5917f3
MT
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
10b3cc9c
MT
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
badb4cf2
MT
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
95a7e4d9 603 return parser.parse_args()
c62d93f1 604
755dd8db 605 def handle_build(self, ns):
b913e798
MT
606 # Create a temporary directory.
607 temp_dir = tempfile.mkdtemp()
608
755dd8db
MT
609 # Format arches.
610 if ns.arch:
611 arches = self.args.arch.split(",")
612 else:
613 arches = None
614
b913e798 615 try:
755dd8db
MT
616 # Package all packages first and save the actual filenames
617 packages = []
b913e798 618
755dd8db
MT
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)
b913e798 625
755dd8db
MT
626 elif package.endswith(".%s" % PACKAGE_EXTENSION):
627 packages.append(package)
c62d93f1 628
755dd8db
MT
629 else:
630 raise Exception("Unknown filetype: %s" % package)
b913e798 631
755dd8db
MT
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)
c62d93f1 637
755dd8db
MT
638 # Show information about the uploaded build
639 summary = build.dump()
640 for line in summary.splitlines():
641 print(" %s" % line)
b913e798
MT
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)
c62d93f1 647
6e3e13fb
MT
648 def handle_check_connection(self, ns):
649 success = self.client.check_connection()
c62d93f1 650
6e3e13fb
MT
651 if success:
652 print("%s: %s" % (_("Connection OK"), success))
c62d93f1 653
ec5917f3
MT
654 def handle_upload(self, ns):
655 for path in ns.file:
656 self.client.upload_file(path)
657
10b3cc9c
MT
658 def handle_watch_build(self, ns):
659 build = self.client.get_build(ns.id[0])
660
badb4cf2
MT
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):
10b3cc9c 669 while True:
badb4cf2 670 s = o.dump()
10b3cc9c
MT
671 print(s)
672
badb4cf2 673 # Break the loop if the build/job is not active any more
10b3cc9c 674 # (since we don't expect any changes)
badb4cf2 675 if not o.is_active():
10b3cc9c
MT
676 break
677
678 time.sleep(60)
679
8f1c63a8
MT
680 # Update data before the next loop is shown
681 o.refresh()
682
c62d93f1
MT
683
684class CliDaemon(Cli):
4ceab0c3
MT
685 def parse_cli(self):
686 parser = argparse.ArgumentParser(
687 description = _("Pakfire daemon command line interface"),
c62d93f1 688 )
4ceab0c3 689 self._add_common_arguments(parser, offline_switch=False)
c62d93f1 690
4ceab0c3
MT
691 # There is only one default action
692 parser.set_defaults(func=self.handle_run)
c62d93f1 693
4ceab0c3
MT
694 return parser.parse_args()
695
696 def handle_run(self, ns):
c62d93f1 697 """
4ceab0c3 698 Runs the pakfire daemon
c62d93f1 699 """
4ceab0c3
MT
700 d = daemon.PakfireDaemon()
701
c62d93f1
MT
702 try:
703 d.run()
704
4ceab0c3 705 # We cannot just kill the daemon, it needs a smooth shutdown
c62d93f1
MT
706 except (SystemExit, KeyboardInterrupt):
707 d.shutdown()
68c0e769
MT
708
709
710class CliKey(Cli):
0a89bb8a
MT
711 def parse_cli(self):
712 parser = argparse.ArgumentParser(
713 description = _("Pakfire key command line interface"),
68c0e769 714 )
0a89bb8a 715 subparsers = parser.add_subparsers()
68c0e769 716
0a89bb8a
MT
717 # Add common arguments
718 self._add_common_arguments(parser)
68c0e769 719
b9b6a13e
MT
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
7b526dd2
MT
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
d36b1022 734 # import
7b526dd2
MT
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
0a89bb8a
MT
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
0a89bb8a 751 return parser.parse_args()
68c0e769 752
68c0e769
MT
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
0a89bb8a
MT
773 def handle_generate(self, ns):
774 p = self.pakfire(ns)
68c0e769 775
964aa579
MT
776 print(_("Generating the key may take a moment..."))
777 print()
68c0e769 778
ad62bb07 779 key = p.generate_key("%s <%s>" % (ns.realname.pop(), ns.email.pop()))
0a89bb8a
MT
780
781 def handle_list(self, ns):
782 p = self.pakfire(ns)
783
ad62bb07 784 for key in p.keys:
0a89bb8a
MT
785 print(key)
786
787 def handle_export(self, ns):
788 p = self.pakfire(ns)
789
790 for fingerprint in ns.fingerprint:
ad62bb07 791 key = p.get_key(fingerprint)
0a89bb8a
MT
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)
68c0e769 803
7b526dd2
MT
804 def handle_import(self, ns):
805 p = self.pakfire(ns)
68c0e769 806
7b526dd2
MT
807 for filename in ns.filename:
808 with open(filename) as f:
809 data = f.read()
810
91f40bc5
MT
811 keys = p.import_key(data)
812 for key in keys:
813 print(key)
68c0e769 814
b9b6a13e
MT
815 def handle_delete(self, ns):
816 p = self.pakfire(ns)
eaf999ef 817
b9b6a13e
MT
818 for fingerprint in ns.fingerprint:
819 key = p.get_key(fingerprint)
820 if key:
821 key.delete()
eaf999ef 822
68c0e769
MT
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:
964aa579 834 raise FileNotFoundError(file)
68c0e769
MT
835
836 key = self.args.key[0]
837
36b328f2 838 # Create pakfire instance.
3a1ddabb 839 p = self.create_pakfire()
36b328f2 840
68c0e769
MT
841 for file in files:
842 # Open the package.
36b328f2 843 pkg = packages.open(p, None, file)
68c0e769 844
964aa579 845 print(_("Signing %s...") % pkg.friendly_name)
68c0e769
MT
846 pkg.sign(key)
847
848 def handle_verify(self):
b4f722d1
MT
849 # TODO needs to use new key code from libpakfire
850
68c0e769
MT
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
36b328f2 860 # Create pakfire instance.
3a1ddabb 861 p = self.create_pakfire()
36b328f2 862
68c0e769
MT
863 for file in files:
864 # Open the package.
36b328f2 865 pkg = packages.open(p, None, file)
68c0e769 866
964aa579 867 print(_("Verifying %s...") % pkg.friendly_name)
68c0e769
MT
868 sigs = pkg.verify()
869
870 for sig in sigs:
3a1ddabb 871 key = p.keyring.get_key(sig.fpr)
68c0e769
MT
872 if key:
873 subkey = key.subkeys[0]
874
964aa579 875 print(" %s %s" % (subkey.fpr[-16:], key.uids[0].uid))
68c0e769 876 if sig.validity:
964aa579 877 print(" %s" % _("This signature is valid."))
68c0e769
MT
878
879 else:
964aa579
MT
880 print(" %s <%s>" % (sig.fpr, _("Unknown key")))
881 print(" %s" % _("Could not check if this signature is valid."))
68c0e769
MT
882
883 created = datetime.datetime.fromtimestamp(sig.timestamp)
964aa579 884 print(" %s" % _("Created: %s") % created)
68c0e769
MT
885
886 if sig.exp_timestamp:
887 expires = datetime.datetime.fromtimestamp(sig.exp_timestamp)
964aa579 888 print(" %s" % _("Expires: %s") % expires)
68c0e769 889
964aa579 890 print() # Empty line