1 #!/usr/bin/python2.4
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 # Use is subject to license terms.
24 #
25 # pkg - package system client utility
26 #
27 # We use urllib2 for GET and POST operations, but httplib for PUT and DELETE
28 # operations.
29 #
30 # The client is going to maintain an on-disk cache of its state, so that
31 # startup assembly of the graph is reduced.
32 #
33 # Client graph is of the entire local catalog. As operations progress, package
34 # states will change.
35 #
36 # Deduction operation allows the compilation of the local component of the
37 # catalog, only if an authoritative repository can identify critical files.
38 #
39 # Environment variables
40 #
41 # PKG_IMAGE - root path of target image
42 # PKG_IMAGE_TYPE [entire, partial, user] - type of image
43 # XXX or is this in the Image configuration?
44
45 import calendar
46 import datetime
47 import errno
48 import getopt
49 import gettext
50 import itertools
51 import locale
52 import os
53 import socket
54 import sys
55 import time
56 import traceback
57 import urllib2
58 import urlparse
59
60 import pkg
61 import pkg.actions as actions
62 import pkg.client.api as api
63 import pkg.client.api_errors as api_errors
64 import pkg.client.bootenv as bootenv
65 import pkg.client.history as history
66 import pkg.client.image as image
67 import pkg.client.imagetypes as imgtypes
68 import pkg.client.progress as progress
69 import pkg.client.publisher as publisher
70 import pkg.fmri as fmri
71 import pkg.misc as misc
72
73 from pkg.client import global_settings
74 from pkg.client.debugvalues import DebugValues
75 from pkg.client.history import (RESULT_CANCELED, RESULT_FAILED_BAD_REQUEST,
76 RESULT_FAILED_CONFIGURATION, RESULT_FAILED_TRANSPORT, RESULT_FAILED_UNKNOWN,
77 RESULT_FAILED_OUTOFMEMORY)
78 from pkg.misc import EmptyI, msg, emsg, PipeError
79
80 CLIENT_API_VERSION = 15
81 PKG_CLIENT_NAME = "pkg"
82
83 def error(text, cmd=None):
84 """Emit an error message prefixed by the command name """
85
86 if cmd:
87 text = "%s: %s" % (cmd, text)
88 else:
89 # If we get passed something like an Exception, we can convert
90 # it down to a string.
91 text = str(text)
92
93 # If the message starts with whitespace, assume that it should come
94 # *before* the command-name prefix.
95 text_nows = text.lstrip()
96 ws = text[:len(text) - len(text_nows)]
97
98 # This has to be a constant value as we can't reliably get our actual
99 # program name on all platforms.
100 emsg(ws + "pkg: " + text_nows)
101
102 def usage(usage_error=None, cmd=None, retcode=2):
103 """Emit a usage message and optionally prefix it with a more
104 specific error message. Causes program to exit. """
105
106 if usage_error:
107 error(usage_error, cmd=cmd)
108
109 emsg(_("""\
110 Usage:
111 pkg [options] command [cmd_options] [operands]
112
113 Basic subcommands:
114 pkg install [-nvq] [--no-refresh] [--no-index] package...
115 pkg uninstall [-nrvq] [--no-index] package...
116 pkg list [-Hafsuv] [--no-refresh] [package...]
117 pkg image-update [-fnvq] [--be-name name] [--no-refresh] [--no-index]
118 pkg refresh [--full] [publisher ...]
119 pkg version
120
121 Advanced subcommands:
122 pkg info [-lr] [--license] [pkg_fmri_pattern ...]
123 pkg search [-alprI] [-s server] query
124 pkg verify [-Hqv] [pkg_fmri_pattern ...]
125 pkg fix [pkg_fmri_pattern ...]
126 pkg contents [-Hmr] [-o attribute ...] [-s sort_key]
127 [-t action_type ... ] [pkg_fmri_pattern ...]
128 pkg image-create [-fFPUz] [--force] [--full|--partial|--user] [--zone]
129 [-k ssl_key] [-c ssl_cert] [--no-refresh]
130 [--variant <variant_spec>=<instance>]
131 (-p|--publisher) name=uri dir
132
133 pkg set-property propname propvalue
134 pkg unset-property propname ...
135 pkg property [-H] [propname ...]
136
137 pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert]
138 [-O origin_uri] [-m mirror_to_add | --add-mirror=mirror_to_add]
139 [-M mirror_to_remove | --remove-mirror=mirror_to_remove]
140 [--enable] [--disable] [--no-refresh] [--reset-uuid] publisher
141 pkg unset-publisher publisher ...
142 pkg publisher [-HPa] [publisher ...]
143 pkg history [-Hl]
144 pkg purge-history
145 pkg rebuild-index
146
147 Options:
148 -R dir
149 --help or -?
150
151 Environment:
152 PKG_IMAGE"""))
153 sys.exit(retcode)
154
155 # XXX Subcommands to implement:
156 # pkg image-set name value
157 # pkg image-unset name
158 # pkg image-get [name ...]
159
160 INCONSISTENT_INDEX_ERROR_MESSAGE = "The search index appears corrupted. " + \
161 "Please rebuild the index with 'pkg rebuild-index'."
162
163 PROBLEMATIC_PERMISSIONS_ERROR_MESSAGE = "\n(Failure of consistent use " + \
164 "of pfexec when executing pkg commands is often a\nsource of this problem.)"
165
166 def check_fmri_args(args):
167 """ Convenience routine to check that input args are valid fmris. """
168 ret = True
169 for x in args:
170 try:
171 #
172 # Pass a bogus build release-- needed to satisfy
173 # fmri's checks in the common case that a version but
174 # no build release was specified by the user.
175 #
176 fmri.MatchingPkgFmri(x, build_release="1.0")
177 except fmri.IllegalFmri, e:
178 error(e)
179 ret = False
180 return ret
181
182 def list_inventory(img, args):
183 """List packages."""
184
185 all_known = False
186 all_versions = True
187 display_headers = True
188 refresh_catalogs = True
189 summary = False
190 upgradable_only = False
191 verbose = False
192
193 opts, pargs = getopt.getopt(args, "Hafsuv", ["no-refresh"])
194
195 for opt, arg in opts:
196 if opt == "-a":
197 all_known = True
198 elif opt == "-H":
199 display_headers = False
200 elif opt == "-s":
201 summary = True
202 elif opt == "-u":
203 upgradable_only = True
204 elif opt == "-v":
205 verbose = True
206 elif opt == "-f":
207 all_versions = False
208 elif opt == "--no-refresh":
209 refresh_catalogs = False
210
211 if summary and verbose:
212 usage(_("-s and -v may not be combined"))
213
214 if verbose:
215 fmt_str = "%-64s %-10s %s"
216 elif summary:
217 fmt_str = "%-30s %s"
218 else:
219 fmt_str = "%-45s %-15s %-10s %s"
220
221 if not check_fmri_args(pargs):
222 return 1
223
224 img.history.operation_name = "list"
225 img.load_catalogs(progress.NullProgressTracker())
226
227 api_inst = api.ImageInterface(img.get_root(), CLIENT_API_VERSION,
228 get_tracker(quiet=True), None, PKG_CLIENT_NAME)
229 info_needed = frozenset([api.PackageInfo.SUMMARY])
230
231 seen_one_pkg = False
232 found = False
233 try:
234 if all_known and refresh_catalogs:
235 # If the user requested all known packages, ensure that
236 # a publisher metadata refresh is performed if needed
237 # since the catalog may be out of date or invalid as
238 # a result of publisher information changing (such as
239 # an origin uri, etc.).
240 tracker = get_tracker(quiet=not display_headers)
241 try:
242 img.refresh_publishers(progtrack=tracker)
243 except KeyboardInterrupt:
244 raise
245 except:
246 # Ignore the above error and just use what
247 # already exists.
248 pass
249
250 res = misc.get_inventory_list(img, pargs,
251 all_known, all_versions)
252 prev_pfmri_str = ""
253 prev_state = None
254 for pfmri, state in res:
255 if all_versions and prev_pfmri_str and \
256 prev_pfmri_str == pfmri.get_short_fmri() and \
257 prev_state == state:
258 continue
259 prev_pfmri_str = pfmri.get_short_fmri()
260 prev_state = state
261 seen_one_pkg = True
262 if upgradable_only and not state["upgradable"]:
263 continue
264
265 if not found:
266 if display_headers:
267 if verbose:
268 msg(fmt_str % \
269 ("FMRI", "STATE", "UFIX"))
270 elif summary:
271 msg(fmt_str % \
272 ("NAME (PUBLISHER)",
273 "SUMMARY"))
274 else:
275 msg(fmt_str % \
276 ("NAME (PUBLISHER)",
277 "VERSION", "STATE", "UFIX"))
278 found = True
279 ufix = "%c%c%c%c" % \
280 (state["upgradable"] and "u" or "-",
281 state["frozen"] and "f" or "-",
282 state["incorporated"] and "i" or "-",
283 state["excludes"] and "x" or "-")
284
285 if pfmri.preferred_publisher():
286 pub = ""
287 else:
288 pub = " (" + pfmri.get_publisher() + ")"
289
290 if verbose:
291 pf = pfmri.get_fmri(
292 img.get_preferred_publisher())
293 msg("%-64s %-10s %s" % (pf, state["state"],
294 ufix))
295 elif summary:
296 pf = pfmri.get_name() + pub
297
298 try:
299 ret = api_inst.info([pfmri], False,
300 info_needed)
301 pis = ret[api.ImageInterface.INFO_FOUND]
302 except api_errors.ApiException, e:
303 error(e)
304 return 1
305
306 msg(fmt_str % (pf, pis[0].summary))
307
308 else:
309 pf = pfmri.get_name() + pub
310 msg(fmt_str % (pf, pfmri.get_version(),
311 state["state"], ufix))
312
313 if not found:
314 if not seen_one_pkg and not all_known:
315 emsg(_("no packages installed"))
316 img.history.operation_result = \
317 history.RESULT_NOTHING_TO_DO
318 return 1
319
320 if upgradable_only:
321 if pargs:
322 emsg(_("No specified packages have " \
323 "available updates"))
324 else:
325 emsg(_("No installed packages have " \
326 "available updates"))
327 img.history.operation_result = \
328 history.RESULT_NOTHING_TO_DO
329 return 1
330
331 img.history.operation_result = \
332 history.RESULT_NOTHING_TO_DO
333 return 1
334
335 img.history.operation_result = history.RESULT_SUCCEEDED
336 return 0
337
338 except api_errors.InventoryException, e:
339 if e.illegal:
340 for i in e.illegal:
341 error(i)
342 img.history.operation_result = \
343 history.RESULT_FAILED_BAD_REQUEST
344 return 1
345
346 if all_known:
347 state = image.PKG_STATE_KNOWN
348 else:
349 state = image.PKG_STATE_INSTALLED
350 for pat in e.notfound:
351 error(_("no packages matching "
352 "'%(pattern)s' %(state)s") %
353 { "pattern": pat, "state": state })
354 img.history.operation_result = history.RESULT_NOTHING_TO_DO
355 return 1
356
357 def get_tracker(quiet=False):
358 if quiet:
359 progresstracker = progress.QuietProgressTracker()
360 else:
361 try:
362 progresstracker = \
363 progress.FancyUNIXProgressTracker()
364 except progress.ProgressTrackerException:
365 progresstracker = progress.CommandLineProgressTracker()
366 return progresstracker
367
368 def fix_image(img, args):
369 progresstracker = get_tracker(False)
370 img.load_catalogs(progresstracker)
371 fmris, notfound, illegals = img.installed_fmris_from_args(args)
372
373 any_errors = False
374 repairs = []
375 for f in fmris:
376 failed_actions = []
377 for err in img.verify(f, progresstracker,
378 verbose=True, forever=True):
379 if not failed_actions:
380 msg("Verifying: %-50s %7s" %
381 (f.get_pkg_stem(), "ERROR"))
382 act = err[0]
383 failed_actions.append(act)
384 msg("\t%s" % act.distinguished_name())
385 for x in err[1]:
386 msg("\t\t%s" % x)
387 if failed_actions:
388 repairs.append((f, failed_actions))
389
390 # Repair anything we failed to verify
391 if repairs:
392 # Create a snapshot in case they want to roll back
393 try:
394 be = bootenv.BootEnv(img.get_root())
395 if be.exists():
396 msg(_("Created ZFS snapshot: %s" %
397 be.snapshot_name))
398 except RuntimeError:
399 pass # Error is printed by the BootEnv call.
400 success = img.repair(repairs, progresstracker)
401 if not success:
402 progresstracker.verify_done()
403 return 1
404 progresstracker.verify_done()
405 return 0
406
407 def verify_image(img, args):
408 opts, pargs = getopt.getopt(args, "vfqH")
409
410 quiet = verbose = False
411 # for now, always check contents of files
412 forever = display_headers = True
413
414 for opt, arg in opts:
415 if opt == "-H":
416 display_headers = False
417 if opt == "-v":
418 verbose = True
419 elif opt == "-f":
420 forever = True
421 elif opt == "-q":
422 quiet = True
423 display_headers = False
424
425 if verbose and quiet:
426 usage(_("verify: -v and -q may not be combined"))
427
428 progresstracker = get_tracker(quiet)
429
430 if not check_fmri_args(pargs):
431 return 1
432
433 img.load_catalogs(progresstracker)
434
435 fmris, notfound, illegals = img.installed_fmris_from_args(pargs)
436
437 if illegals:
438 for i in illegals:
439 emsg(str(i))
440 return 1
441
442 any_errors = False
443
444 header = False
445 for f in fmris:
446 pkgerr = False
447 for err in img.verify(f, progresstracker,
448 verbose=verbose, forever=forever):
449 #
450 # Eventually this code should probably
451 # move into the progresstracker
452 #
453 if not pkgerr:
454 if display_headers and not header:
455 msg("%-50s %7s" % ("PACKAGE", "STATUS"))
456 header = True
457
458 if not quiet:
459 msg("%-50s %7s" % (f.get_pkg_stem(),
460 "ERROR"))
461 pkgerr = True
462
463 if not quiet:
464 msg("\t%s" % err[0].distinguished_name())
465 for x in err[1]:
466 msg("\t\t%s" % x)
467 if verbose and not pkgerr:
468 if display_headers and not header:
469 msg("%-50s %7s" % ("PACKAGE", "STATUS"))
470 header = True
471 msg("%-50s %7s" % (f.get_pkg_stem(), "OK"))
472
473 any_errors = any_errors or pkgerr
474
475 if fmris:
476 progresstracker.verify_done()
477
478 if notfound:
479 if fmris:
480 emsg()
481 emsg(_("""\
482 pkg: no packages matching the following patterns you specified are
483 installed on the system.\n"""))
484 for p in notfound:
485 emsg(" %s" % p)
486 if fmris:
487 if any_errors:
488 msg2 = "See above for\nverification failures."
489 else:
490 msg2 = "No packages failed\nverification."
491 emsg(_("\nAll other patterns matched installed "
492 "packages. %s" % msg2))
493 any_errors = True
494
495 if any_errors:
496 return 1
497 return 0
498
499 def image_update(img_dir, args):
500 """Attempt to take all installed packages specified to latest
501 version."""
502
503 # XXX Publisher-catalog issues.
504 # XXX Are filters appropriate for an image update?
505 # XXX Leaf package refinements.
506
507 opts, pargs = getopt.getopt(args, "fnvq", ["be-name=", "no-refresh",
508 "no-index"])
509
510 force = quiet = noexecute = verbose = False
511 refresh_catalogs = update_index = True
512 be_name = None
513 for opt, arg in opts:
514 if opt == "-n":
515 noexecute = True
516 elif opt == "-v":
517 verbose = True
518 elif opt == "-q":
519 quiet = True
520 elif opt == "-f":
521 force = True
522 elif opt == "--no-refresh":
523 refresh_catalogs = False
524 elif opt == "--no-index":
525 update_index = False
526 elif opt == "--be-name":
527 be_name = arg
528
529 if verbose and quiet:
530 usage(_("image-update: -v and -q may not be combined"))
531
532 if pargs:
533 usage(_("image-update: command does not take operands " \
534 "('%s')") % " ".join(pargs))
535
536 progresstracker = get_tracker(quiet)
537
538 try:
539 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
540 progresstracker, cancel_state_callable=None,
541 pkg_client_name=PKG_CLIENT_NAME)
542 except api_errors.ImageNotFoundException, e:
543 error(_("No image rooted at '%s'") % e.user_dir)
544 return 1
545
546 try:
547 # cre is either None or a catalog refresh exception which was
548 # caught while planning.
549 stuff_to_do, opensolaris_image, cre = \
550 api_inst.plan_update_all(sys.argv[0], refresh_catalogs,
551 noexecute, force=force, verbose=verbose,
552 update_index=update_index, be_name=be_name)
553 if cre and not display_catalog_failures(cre):
554 raise RuntimeError("Catalog refresh failed during"
555 " image-update.")
556 if not stuff_to_do:
557 msg(_("No updates available for this image."))
558 return 0
559 except api_errors.InventoryException, e:
560 error(_("image-update failed (inventory exception):\n%s") % e)
561 return 1
562 except api_errors.CatalogRefreshException, e:
563 if display_catalog_failures(e) == 0:
564 if not noexecute:
565 return 1
566 else:
567 raise RuntimeError("Catalog refresh failed during"
568 " image-update.")
569 except api_errors.BEException, e:
570 error(_(e))
571 return 1
572 except (api_errors.CertificateError,
573 api_errors.PlanCreationException,
574 api_errors.PermissionsException), e:
575 # Prepend a newline because otherwise the exception will
576 # be printed on the same line as the spinner.
577 error("\n" + str(e))
578 return 1
579 except api_errors.IpkgOutOfDateException:
580 msg(_("WARNING: pkg(5) appears to be out of date, and should " \
581 "be updated before\nrunning image-update.\n"))
582 msg(_("Please update pkg(5) using 'pfexec pkg install " \
583 "SUNWipkg' and then retry\nthe image-update."))
584 return 1
585 except api_errors.ImageNotFoundException, e:
586 error(_("No image rooted at '%s'") % e.user_dir)
587 return 1
588 if noexecute:
589 return 0
590
591 ret_code = 0
592
593 # Exceptions which happen here are printed in the above level, with
594 # or without some extra decoration done here.
595 # XXX would be nice to kick the progress tracker.
596 try:
597 api_inst.prepare()
598 except api_errors.TransportError, e:
599 # move past the progress tracker line.
600 msg("\n")
601 if verbose:
602 e.verbose = True
603 raise e
604 except KeyboardInterrupt:
605 raise
606 except api_errors.PermissionsException, e:
607 # Prepend a newline because otherwise the exception will
608 # be printed on the same line as the spinner.
609 error("\n" + str(e))
610 return 1
611 except:
612 error(_("\nAn unexpected error happened while preparing for " \
613 "image-update:"))
614 raise
615
616 try:
617 api_inst.execute_plan()
618 except RuntimeError, e:
619 error(_("image-update failed: %s") % e)
620 ret_code = 1
621 except api_errors.ImageUpdateOnLiveImageException:
622 error(_("image-update cannot be done on live image"))
623 ret_code = 1
624 except api_errors.CorruptedIndexException, e:
625 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
626 ret_code = 1
627 except api_errors.ProblematicPermissionsIndexException, e:
628 error(str(e) + PROBLEMATIC_PERMISSIONS_ERROR_MESSAGE)
629 ret_code = 1
630 except api_errors.PermissionsException, e:
631 # Prepend a newline because otherwise the exception will
632 # be printed on the same line as the spinner.
633 error("\n" + str(e))
634 ret_code = 1
635 except api_errors.MainDictParsingException, e:
636 error(str(e))
637 ret_code = 1
638 except api_errors.BEException, e:
639 error(_(e))
640 return 1
641 except KeyboardInterrupt:
642 raise
643 except Exception, e:
644 error(_("\nAn unexpected error happened during " \
645 "image-update: %s") % e)
646 raise
647
648 if ret_code == 0 and opensolaris_image:
649 msg("\n" + "-" * 75)
650 msg(_("NOTE: Please review release notes posted at:\n" ))
651 msg(misc.get_release_notes_url())
652 msg("-" * 75 + "\n")
653
654 if bool(os.environ.get("PKG_MIRROR_STATS", False)):
655 print_mirror_stats(api_inst)
656
657 return ret_code
658
659 def print_mirror_stats(api_inst):
660 """Given an api_inst object, print depot status information."""
661
662 status_fmt = "%-10s %-35s %10s %10s"
663 print status_fmt % ("Publisher", "URI", "Success", "Failure")
664
665 for ds in api_inst.img.gen_depot_status():
666 print status_fmt % (ds.prefix, ds.url, ds.good_tx, ds.errors)
667
668 def install(img_dir, args):
669 """Attempt to take package specified to INSTALLED state. The operands
670 are interpreted as glob patterns."""
671
672 # XXX Publisher-catalog issues.
673
674 opts, pargs = getopt.getopt(args, "nvf:q", ["no-refresh", "no-index"])
675
676 quiet = noexecute = verbose = False
677 refresh_catalogs = update_index = True
678 filters = []
679 for opt, arg in opts:
680 if opt == "-n":
681 noexecute = True
682 elif opt == "-v":
683 verbose = True
684 elif opt == "-f":
685 filters += [ arg ]
686 elif opt == "-q":
687 quiet = True
688 elif opt == "--no-refresh":
689 refresh_catalogs = False
690 elif opt == "--no-index":
691 update_index = False
692
693 if not pargs:
694 usage(_("install: at least one package name required"))
695
696 if verbose and quiet:
697 usage(_("install: -v and -q may not be combined"))
698
699 progresstracker = get_tracker(quiet)
700
701 if not check_fmri_args(pargs):
702 return 1
703
704 # XXX not sure where this should live
705 pkg_list = [ pat.replace("*", ".*").replace("?", ".")
706 for pat in pargs ]
707
708 try:
709 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
710 progresstracker, None, PKG_CLIENT_NAME)
711 except api_errors.ImageNotFoundException, e:
712 error(_("'%s' is not an install image") % e.user_dir)
713 return 1
714
715 try:
716 # cre is either None or a catalog refresh exception which was
717 # caught while planning.
718 stuff_to_do, cre = api_inst.plan_install(pkg_list, filters,
719 refresh_catalogs, noexecute, verbose=verbose,
720 update_index=update_index)
721 if cre and not display_catalog_failures(cre):
722 raise RuntimeError("Catalog refresh failed during"
723 " install.")
724 if not stuff_to_do:
725 msg(_("No updates available for this image."))
726 return 0
727 except api_errors.CatalogRefreshException, e:
728 if display_catalog_failures(e) == 0:
729 if not noexecute:
730 return 1
731 else:
732 error(_("Catalog refresh failed during install."),
733 cmd="install")
734 return 1
735 except (api_errors.CertificateError,
736 api_errors.PlanCreationException,
737 api_errors.PermissionsException), e:
738 # Prepend a newline because otherwise the exception will
739 # be printed on the same line as the spinner.
740 error("\n" + str(e), cmd="install")
741 return 1
742 except api_errors.InventoryException, e:
743 error(_("install failed (inventory exception):\n%s") % e,
744 cmd="install")
745 return 1
746 except fmri.IllegalFmri, e:
747 error(e, cmd="install")
748 return 1
749
750 if noexecute:
751 return 0
752
753 # Exceptions which happen here are printed in the above level, with
754 # or without some extra decoration done here.
755 # XXX would be nice to kick the progress tracker.
756 try:
757 api_inst.prepare()
758 except api_errors.TransportError, e:
759 # move past the progress tracker line.
760 msg("\n")
761 if verbose:
762 e.verbose = True
763 raise e
764 except KeyboardInterrupt:
765 raise
766 except api_errors.PermissionsException, e:
767 # Prepend a newline because otherwise the exception will
768 # be printed on the same line as the spinner.
769 error("\n" + str(e))
770 return 1
771 except:
772 error(_("\nAn unexpected error happened while preparing for " \
773 "install:"))
774 raise
775
776 ret_code = 0
777
778 try:
779 api_inst.execute_plan()
780 except RuntimeError, e:
781 error(_("installation failed: %s") % e)
782 ret_code = 1
783 except api_errors.CorruptedIndexException, e:
784 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
785 ret_code = 1
786 except api_errors.ProblematicPermissionsIndexException, e:
787 error(str(e) + PROBLEMATIC_PERMISSIONS_ERROR_MESSAGE)
788 ret_code = 1
789 except api_errors.PermissionsException, e:
790 # Prepend a newline because otherwise the exception will
791 # be printed on the same line as the spinner.
792 error("\n" + str(e))
793 ret_code = 1
794 except api_errors.MainDictParsingException, e:
795 error(str(e))
796 ret_code = 1
797 except KeyboardInterrupt:
798 raise
799 except api_errors.ActionExecutionError:
800 ret_code = 1
801 except Exception, e:
802 error(_("An unexpected error happened during " \
803 "installation: %s") % e)
804 raise
805
806 if bool(os.environ.get("PKG_MIRROR_STATS", False)):
807 print_mirror_stats(api_inst)
808
809 return ret_code
810
811
812 def uninstall(img_dir, args):
813 """Attempt to take package specified to DELETED state."""
814
815 opts, pargs = getopt.getopt(args, "nrvq", ["no-index"])
816
817 quiet = noexecute = recursive_removal = verbose = False
818 update_index = True
819 for opt, arg in opts:
820 if opt == "-n":
821 noexecute = True
822 elif opt == "-r":
823 recursive_removal = True
824 elif opt == "-v":
825 verbose = True
826 elif opt == "-q":
827 quiet = True
828 elif opt == "--no-index":
829 update_index = False
830
831 if not pargs:
832 usage(_("uninstall: at least one package name required"))
833
834 if verbose and quiet:
835 usage(_("uninstall: -v and -q may not be combined"))
836
837 progresstracker = get_tracker(quiet)
838
839 if not check_fmri_args(pargs):
840 return 1
841
842 # XXX not sure where this should live
843 pkg_list = [ pat.replace("*", ".*").replace("?", ".")
844 for pat in pargs ]
845
846 try:
847 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
848 progresstracker, None, PKG_CLIENT_NAME)
849 except api_errors.ImageNotFoundException, e:
850 error(_("'%s' is not an install image") % e.user_dir)
851 return 1
852
853 try:
854 if not api_inst.plan_uninstall(pkg_list, recursive_removal,
855 noexecute, verbose=verbose, update_index=update_index):
856 assert 0
857 except api_errors.InventoryException, e:
858 error(_("uninstall failed (inventory exception):\n%s") % e)
859 return 1
860 except api_errors.NonLeafPackageException, e:
861 error("""Cannot remove '%s' due to
862 the following packages that depend on it:""" % e[0])
863 for d in e[1]:
864 emsg(" %s" % d)
865 return 1
866 except (api_errors.PlanCreationException,
867 api_errors.PermissionsException), e:
868 # Prepend a newline because otherwise the exception will
869 # be printed on the same line as the spinner.
870 error("\n" + str(e))
871 return 1
872
873 if noexecute:
874 return 0
875
876 # Exceptions which happen here are printed in the above level, with
877 # or without some extra decoration done here.
878 # XXX would be nice to kick the progress tracker.
879 try:
880 api_inst.prepare()
881 except api_errors.TransportError, e:
882 # move past the progress tracker line.
883 msg("\n")
884 if verbose:
885 e.verbose = True
886 raise e
887 except api_errors.FileInUseException, e:
888 error("\n" + str(e))
889 return 1
890 except KeyboardInterrupt:
891 raise
892 except:
893 error(_("\nAn unexpected error happened while preparing for " \
894 "install:"))
895 raise
896
897 ret_code = 0
898
899 try:
900 api_inst.execute_plan()
901 except RuntimeError, e:
902 error(_("uninstallation failed: %s") % e)
903 ret_code = 1
904 except api_errors.CorruptedIndexException, e:
905 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
906 ret_code = 1
907 except api_errors.ProblematicPermissionsIndexException, e:
908 error(str(e) + PROBLEMATIC_PERMISSIONS_ERROR_MESSAGE)
909 ret_code = 1
910 except api_errors.PermissionsException, e:
911 # Prepend a newline because otherwise the exception will
912 # be printed on the same line as the spinner.
913 error("\n" + str(e))
914 ret_code = 1
915 except api_errors.MainDictParsingException, e:
916 error(str(e))
917 ret_code = 1
918 except KeyboardInterrupt:
919 raise
920 except Exception, e:
921 error(_("An unexpected error happened during " \
922 "uninstallation: %s") % e)
923 raise
924
925 return ret_code
926
927 def freeze(img, args):
928 """Attempt to take package specified to FROZEN state, with given
929 restrictions. Package must have been in the INSTALLED state."""
930 return 0
931
932 def unfreeze(img, args):
933 """Attempt to return package specified to INSTALLED state from FROZEN
934 state."""
935 return 0
936
937 def __convert_output(a_str, match):
938 """Converts a string to a three tuple with the information to fill
939 the INDEX, ACTION, and VALUE columns.
940
941 The "a_str" parameter is the string representation of an action.
942
943 The "match" parameter is a string whose precise interpretation is given
944 below.
945
946 For most action types, match defines which attribute the query matched
947 with. For example, it states whether the basename or path attribute of
948 a file action matched the query. Attribute (set) actions are treated
949 differently because they only have one attribute, and many values
950 associated with that attribute. For those actions, the match parameter
951 states which value matched the query."""
952
953 a = actions.fromstr(a_str.rstrip())
954 if isinstance(a, actions.attribute.AttributeAction):
955 return a.attrs.get(a.key_attr), a.name, match
956 return match, a.name, a.attrs.get(a.key_attr)
957
958 def process_v_1_search(tup, first, return_type, pub):
959 """Transforms the tuples returned by search v1 into the four column
960 output format.
961
962 The "first" parameter is a boolean stating whether this is the first
963 time this function has been called. This controls the printing of the
964 header information.
965
966 The "return_type" parameter is an enumeration that describes the type
967 of the information that will be converted.
968
969 The type of the "tup" parameter depends on the value of "return_type".
970 If "return_type" is action information, "tup" is a three-tuple of the fmri
971 name, the match, and a string representation of the action. In the case
972 where "return_type" is package information, "tup" is a one-tuple containing
973 the fmri name."""
974
975 if return_type == api.Query.RETURN_ACTIONS:
976 try:
977 pfmri, match, action = tup
978 except ValueError:
979 error(_("The server returned a malformed result.\n"
980 "The problematic structure:%r") % (tup,))
981 return False
982 if first:
983 msg("%-10s %-9s %-25s %s" %
984 ("INDEX", "ACTION", "VALUE", "PACKAGE"))
985 try:
986 out1, out2, out3 = __convert_output(action, match)
987 except (actions.UnknownActionError,
988 actions.MalformedActionError), e:
989 error(_("The server returned a malformed action.\n%s") %
990 e)
991 return False
992 msg("%-10s %-9s %-25s %s" %
993 (out1, out2, out3,
994 fmri.PkgFmri(str(pfmri)).get_short_fmri()))
995 else:
996 pfmri = tup
997 if first:
998 msg("%s" % ("PACKAGE"))
999 pub_name = ''
1000 # If pub is not None, it's either a RepositoryURI or a Publisher
1001 # object. If it's a Publisher, it has a prefix. Otherwise,
1002 # use the uri.
1003 if pub is not None and hasattr(pub, "prefix"):
1004 pub_name = " (%s)" % pub.prefix
1005 elif pub is not None and hasattr(pub, "uri"):
1006 pub_name = " (%s)" % pub.uri
1007 msg("%s%s" %
1008 (fmri.PkgFmri(str(pfmri)).get_short_fmri(), pub_name))
1009 return True
1010
1011 def search(img_dir, args):
1012 """Search for the given query."""
1013
1014 opts, pargs = getopt.getopt(args, "alprs:I")
1015
1016 local = remote = case_sensitive = False
1017 servers = []
1018 return_actions = True
1019 for opt, arg in opts:
1020 if opt == "-a":
1021 return_actions = True
1022 elif opt == "-l":
1023 local = True
1024 elif opt == "-p":
1025 return_actions = False
1026 elif opt == "-r":
1027 remote = True
1028 elif opt == "-s":
1029 if not misc.valid_pub_url(arg):
1030 orig_arg = arg
1031 arg = "http://" + arg
1032 if not misc.valid_pub_url(arg):
1033 error(_("%s is not a valid "
1034 "server URL.") % orig_arg)
1035 return 1
1036 remote = True
1037 servers.append({"origin": arg})
1038 elif opt == "-I":
1039 case_sensitive = True
1040
1041 if not local and not remote:
1042 remote = True
1043
1044 if not pargs:
1045 usage()
1046
1047 searches = []
1048
1049 try:
1050 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1051 get_tracker(), None, PKG_CLIENT_NAME)
1052 except api_errors.ImageNotFoundException, e:
1053 error(_("'%s' is not an install image") % e.user_dir)
1054 return 1
1055 try:
1056 query = [api.Query(" ".join(pargs), case_sensitive,
1057 return_actions)]
1058 except api_errors.BooleanQueryException, e:
1059 error(e)
1060 return 1
1061
1062 first = True
1063 good_res = False
1064 bad_res = False
1065
1066 try:
1067 if local:
1068 searches.append(api_inst.local_search(query))
1069 if remote:
1070 searches.append(api_inst.remote_search(query,
1071 servers=servers))
1072
1073 # By default assume we don't find anything.
1074 retcode = 1
1075
1076 for raw_value in itertools.chain(*searches):
1077 try:
1078 query_num, pub, (v, return_type, tmp) = \
1079 raw_value
1080 except ValueError, e:
1081 error(_("The server returned a malformed "
1082 "result:%r") % (raw_value,))
1083 bad_res = True
1084 continue
1085 ret = process_v_1_search(tmp, first,
1086 return_type, pub)
1087 good_res |= ret
1088 bad_res |= not ret
1089 first = False
1090 except (api_errors.IncorrectIndexFileHash,
1091 api_errors.InconsistentIndexException):
1092 error(_("The search index appears corrupted. Please "
1093 "rebuild the index with 'pkg rebuild-index'."))
1094 return 1
1095 except api_errors.ProblematicSearchServers, e:
1096 error(e)
1097 bad_res = True
1098 except api_errors.SlowSearchUsed, e:
1099 error(e)
1100 except (api_errors.IncorrectIndexFileHash,
1101 api_errors.InconsistentIndexException):
1102 error(_("The search index appears corrupted. Please "
1103 "rebuild the index with 'pkg rebuild-index'."))
1104 return 1
1105 except api_errors.ApiException, e:
1106 error(e)
1107 return 1
1108 if good_res and bad_res:
1109 retcode = 4
1110 elif bad_res:
1111 retcode = 1
1112 elif not first:
1113 retcode = 0
1114 return retcode
1115
1116 def info(img_dir, args):
1117 """Display information about a package or packages.
1118 """
1119
1120 display_license = False
1121 info_local = False
1122 info_remote = False
1123
1124 opts, pargs = getopt.getopt(args, "lr", ["license"])
1125 for opt, arg in opts:
1126 if opt == "-l":
1127 info_local = True
1128 elif opt == "-r":
1129 info_remote = True
1130 elif opt == "--license":
1131 display_license = True
1132
1133 if not info_local and not info_remote:
1134 info_local = True
1135 elif info_local and info_remote:
1136 usage(_("info: -l and -r may not be combined"))
1137
1138 if info_remote and not pargs:
1139 usage(_("info: must request remote info for specific packages"))
1140
1141 if not check_fmri_args(pargs):
1142 return 1
1143
1144 err = 0
1145
1146 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1147 get_tracker(quiet=True), None, PKG_CLIENT_NAME)
1148
1149 try:
1150 info_needed = api.PackageInfo.ALL_OPTIONS
1151 if not display_license:
1152 info_needed = api.PackageInfo.ALL_OPTIONS - \
1153 frozenset([api.PackageInfo.LICENSES])
1154 try:
1155 ret = api_inst.info(pargs, info_local, info_needed)
1156 except api_errors.UnrecognizedOptionsToInfo, e:
1157 error(e)
1158 return 1
1159 pis = ret[api.ImageInterface.INFO_FOUND]
1160 notfound = ret[api.ImageInterface.INFO_MISSING]
1161 illegals = ret[api.ImageInterface.INFO_ILLEGALS]
1162 multi_match = ret[api.ImageInterface.INFO_MULTI_MATCH]
1163
1164 except api_errors.PermissionsException, e:
1165 error(e)
1166 return 1
1167 except api_errors.NoPackagesInstalledException:
1168 error(_("no packages installed"))
1169 return 1
1170
1171 no_licenses = []
1172 for i, pi in enumerate(pis):
1173 if i > 0:
1174 msg("")
1175
1176 if display_license:
1177 if not pi.licenses:
1178 no_licenses.append(pi.fmri)
1179 else:
1180 for lic in pi.licenses:
1181 msg(lic)
1182 continue
1183
1184 if pi.state == api.PackageInfo.INSTALLED:
1185 state = _("Installed")
1186 elif pi.state == api.PackageInfo.NOT_INSTALLED:
1187 state = _("Not installed")
1188 else:
1189 raise RuntimeError("Encountered unknown package "
1190 "information state: %d" % pi.state )
1191 name_str = _(" Name:")
1192 msg(name_str, pi.pkg_stem)
1193 msg(_(" Summary:"), pi.summary)
1194 if pi.category_info_list:
1195 verbose = len(pi.category_info_list) > 1
1196 msg(_(" Category:"),
1197 pi.category_info_list[0].__str__(verbose))
1198 if len(pi.category_info_list) > 1:
1199 for ci in pi.category_info_list[1:]:
1200 msg(" " * len(name_str),
1201 ci.__str__(verbose))
1202
1203 msg(_(" State:"), state)
1204
1205 # XXX even more info on the publisher would be nice?
1206 msg(_(" Publisher:"), pi.publisher)
1207 msg(_(" Version:"), pi.version)
1208 msg(_(" Build Release:"), pi.build_release)
1209 msg(_(" Branch:"), pi.branch)
1210 msg(_("Packaging Date:"), pi.packaging_date)
1211 msg(_(" Size:"), misc.bytes_to_str(pi.size))
1212 msg(_(" FMRI:"), pi.fmri)
1213 # XXX add license/copyright info here?
1214
1215 if notfound:
1216 err = 1
1217 if pis:
1218 emsg()
1219 if info_local:
1220 emsg(_("""\
1221 pkg: no packages matching the following patterns you specified are
1222 installed on the system. Try specifying -r to query remotely:"""))
1223 elif info_remote:
1224 emsg(_("""\
1225 pkg: no packages matching the following patterns you specified were
1226 found in the catalog. Try relaxing the patterns, refreshing, and/or
1227 examining the catalogs:"""))
1228 emsg()
1229 for p in notfound:
1230 emsg(" %s" % p)
1231
1232 if illegals:
1233 err = 1
1234 for i in illegals:
1235 emsg(str(i))
1236
1237 if multi_match:
1238 err = 1
1239 for pfmri, matches in multi_match:
1240 error(_("'%s' matches multiple packages") % pfmri)
1241 for k in matches:
1242 emsg("\t%s" % k)
1243
1244 if no_licenses:
1245 err = 1
1246 error(_("no license information could be found for the "
1247 "following packages:"))
1248 for pfmri in no_licenses:
1249 emsg("\t%s" % pfmri)
1250
1251 return err
1252
1253 def display_contents_results(actionlist, attrs, sort_attrs, action_types,
1254 display_headers):
1255 """Print results of a "list" operation """
1256
1257 # widths is a list of tuples of column width and justification. Start
1258 # with the widths of the column headers.
1259 JUST_UNKN = 0
1260 JUST_LEFT = -1
1261 JUST_RIGHT = 1
1262 widths = [ (len(attr) - attr.find(".") - 1, JUST_UNKN)
1263 for attr in attrs ]
1264 lines = []
1265
1266 for manifest, action in actionlist:
1267 if action_types and action.name not in action_types:
1268 continue
1269 line = []
1270 for i, attr in enumerate(attrs):
1271 just = JUST_UNKN
1272 # As a first approximation, numeric attributes
1273 # are right justified, non-numerics left.
1274 try:
1275 int(action.attrs[attr])
1276 just = JUST_RIGHT
1277 # attribute is non-numeric or is something like
1278 # a list.
1279 except (ValueError, TypeError):
1280 just = JUST_LEFT
1281 # attribute isn't in the list, so we don't know
1282 # what it might be
1283 except KeyError:
1284 pass
1285
1286 if attr in action.attrs:
1287 a = action.attrs[attr]
1288 elif attr == "action.name":
1289 a = action.name
1290 just = JUST_LEFT
1291 elif attr == "action.key":
1292 a = action.attrs[action.key_attr]
1293 just = JUST_LEFT
1294 elif attr == "action.raw":
1295 a = action
1296 just = JUST_LEFT
1297 elif attr == "pkg.name":
1298 a = manifest.fmri.get_name()
1299 just = JUST_LEFT
1300 elif attr == "pkg.fmri":
1301 a = manifest.fmri
1302 just = JUST_LEFT
1303 elif attr == "pkg.shortfmri":
1304 a = manifest.fmri.get_short_fmri()
1305 just = JUST_LEFT
1306 elif attr == "pkg.publisher":
1307 a = manifest.fmri.get_publisher()
1308 just = JUST_LEFT
1309 else:
1310 a = ""
1311
1312 line.append(a)
1313
1314 # XXX What to do when a column's justification
1315 # changes?
1316 if just != JUST_UNKN:
1317 widths[i] = \
1318 (max(widths[i][0], len(str(a))), just)
1319
1320 if line and [l for l in line if str(l) != ""]:
1321 lines.append(line)
1322
1323 sortidx = 0
1324 for i, attr in enumerate(attrs):
1325 if attr == sort_attrs[0]:
1326 sortidx = i
1327 break
1328
1329 # Sort numeric columns numerically.
1330 if widths[sortidx][1] == JUST_RIGHT:
1331 def key_extract(x):
1332 try:
1333 return int(x[sortidx])
1334 except (ValueError, TypeError):
1335 return 0
1336 else:
1337 key_extract = lambda x: x[sortidx]
1338
1339 if display_headers:
1340 headers = []
1341 for i, attr in enumerate(attrs):
1342 headers.append(str(attr.upper()))
1343 widths[i] = \
1344 (max(widths[i][0], len(attr)), widths[i][1])
1345
1346 # Now that we know all the widths, multiply them by the
1347 # justification values to get positive or negative numbers to
1348 # pass to the %-expander.
1349 widths = [ e[0] * e[1] for e in widths ]
1350 fmt = ("%%%ss " * len(widths)) % tuple(widths)
1351
1352 msg((fmt % tuple(headers)).rstrip())
1353 else:
1354 fmt = "%s\t" * len(widths)
1355 fmt.rstrip("\t")
1356
1357 for line in sorted(lines, key=key_extract):
1358 msg((fmt % tuple(line)).rstrip())
1359
1360 def list_contents(img, args):
1361 """List package contents.
1362
1363 If no arguments are given, display for all locally installed packages.
1364 With -H omit headers and use a tab-delimited format; with -o select
1365 attributes to display; with -s, specify attributes to sort on; with -t,
1366 specify which action types to list."""
1367
1368 # XXX Need remote-info option, to request equivalent information
1369 # from repository.
1370
1371 opts, pargs = getopt.getopt(args, "Ho:s:t:mfr")
1372
1373 valid_special_attrs = [ "action.name", "action.key", "action.raw",
1374 "pkg.name", "pkg.fmri", "pkg.shortfmri", "pkg.publisher",
1375 "pkg.size", "pkg.csize" ]
1376
1377 display_headers = True
1378 display_raw = False
1379 remote = False
1380 local = False
1381 attrs = []
1382 sort_attrs = []
1383 action_types = []
1384 for opt, arg in opts:
1385 if opt == "-H":
1386 display_headers = False
1387 elif opt == "-o":
1388 attrs.extend(arg.split(","))
1389 elif opt == "-s":
1390 sort_attrs.append(arg)
1391 elif opt == "-t":
1392 action_types.extend(arg.split(","))
1393 elif opt == "-r":
1394 remote = True
1395 elif opt == "-m":
1396 display_raw = True
1397
1398 if not remote and not local:
1399 local = True
1400 elif local and remote:
1401 usage(_("contents: -l and -r may not be combined"))
1402
1403 if remote and not pargs:
1404 usage(_("contents: must request remote contents for specific "
1405 "packages"))
1406
1407 if not check_fmri_args(pargs):
1408 return 1
1409
1410 if display_raw:
1411 display_headers = False
1412 attrs = [ "action.raw" ]
1413
1414 invalid = set(("-H", "-o", "-t")). \
1415 intersection(set([x[0] for x in opts]))
1416
1417 if len(invalid) > 0:
1418 usage(_("contents: -m and %s may not be specified " \
1419 "at the same time") % invalid.pop())
1420
1421 for a in attrs:
1422 if a.startswith("action.") and not a in valid_special_attrs:
1423 usage(_("Invalid attribute '%s'") % a)
1424
1425 if a.startswith("pkg.") and not a in valid_special_attrs:
1426 usage(_("Invalid attribute '%s'") % a)
1427
1428 img.history.operation_name = "contents"
1429 img.load_catalogs(progress.QuietProgressTracker())
1430
1431 err = 0
1432
1433 if local:
1434 fmris, notfound, illegals = \
1435 img.installed_fmris_from_args(pargs)
1436
1437 if illegals:
1438 for i in illegals:
1439 emsg(i)
1440 img.history.operation_result = \
1441 history.RESULT_FAILED_BAD_REQUEST
1442 return 1
1443
1444 if not fmris and not notfound:
1445 error(_("no packages installed"))
1446 img.history.operation_result = \
1447 history.RESULT_NOTHING_TO_DO
1448 return 1
1449 elif remote:
1450 # Verify validity of certificates before attempting network
1451 # operations
1452 try:
1453 img.check_cert_validity()
1454 except (api_errors.CertificateError,
1455 api_errors.PermissionsException), e:
1456 img.history.log_operation_end(error=e)
1457 return 1
1458
1459 fmris = []
1460 notfound = []
1461
1462 # XXX This loop really needs not to be copied from
1463 # Image.make_install_plan()!
1464 for p in pargs:
1465 try:
1466 matches = list(img.inventory([ p ],
1467 all_known = True))
1468 except api_errors.InventoryException, e:
1469 assert(len(e.notfound) == 1)
1470 notfound.append(e.notfound[0])
1471 continue
1472
1473 pnames = {}
1474 pmatch = []
1475 npnames = {}
1476 npmatch = []
1477 for m, state in matches:
1478 if m.preferred_publisher():
1479 pnames[m.get_pkg_stem()] = 1
1480 pmatch.append(m)
1481 else:
1482 npnames[m.get_pkg_stem()] = 1
1483 npmatch.append(m)
1484
1485 if len(pnames.keys()) > 1:
1486 msg(_("pkg: '%s' matches multiple packages") % \
1487 p)
1488 for k in pnames.keys():
1489 msg("\t%s" % k)
1490 continue
1491 elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
1492 msg(_("pkg: '%s' matches multiple packages") % \
1493 p)
1494 for k in npnames.keys():
1495 msg("\t%s" % k)
1496 continue
1497
1498 # matches is a list reverse sorted by version, so take
1499 # the first; i.e., the latest.
1500 if len(pmatch) > 0:
1501 fmris.append(pmatch[0])
1502 else:
1503 fmris.append(npmatch[0])
1504
1505 #
1506 # If the user specifies no specific attrs, and no specific
1507 # sort order, then we fill in some defaults.
1508 #
1509 if not attrs:
1510 # XXX Possibly have multiple exclusive attributes per column?
1511 # If listing dependencies and files, you could have a path/fmri
1512 # column which would list paths for files and fmris for
1513 # dependencies.
1514 attrs = [ "path" ]
1515
1516 if not sort_attrs:
1517 # XXX reverse sorting
1518 # Most likely want to sort by path, so don't force people to
1519 # make it explicit
1520 if "path" in attrs:
1521 sort_attrs = [ "path" ]
1522 else:
1523 sort_attrs = attrs[:1]
1524
1525 # if we want a raw display (contents -m), disable the automatic
1526 # variant filtering that normally limits working set.
1527
1528 if display_raw:
1529 excludes = EmptyI
1530 else:
1531 excludes = img.list_excludes()
1532
1533 manifests = ( img.get_manifest(f, all_arch=display_raw) for f in fmris )
1534
1535 actionlist = [
1536 (m, a)
1537 for m in manifests
1538 for a in m.gen_actions(excludes)
1539 ]
1540
1541 if fmris:
1542 display_contents_results(actionlist, attrs, sort_attrs,
1543 action_types, display_headers)
1544
1545 if notfound:
1546 err = 1
1547 if fmris:
1548 emsg()
1549 if local:
1550 emsg(_("""\
1551 pkg: no packages matching the following patterns you specified are
1552 installed on the system. Try specifying -r to query remotely:"""))
1553 elif remote:
1554 emsg(_("""\
1555 pkg: no packages matching the following patterns you specified were
1556 found in the catalog. Try relaxing the patterns, refreshing, and/or
1557 examining the catalogs:"""))
1558 emsg()
1559 for p in notfound:
1560 emsg(" %s" % p)
1561 img.history.operation_result = history.RESULT_NOTHING_TO_DO
1562 else:
1563 img.history.operation_result = history.RESULT_SUCCEEDED
1564 return err
1565
1566 def display_catalog_failures(cre):
1567 total = cre.total
1568 succeeded = cre.succeeded
1569
1570 txt = _("pkg: %s/%s catalogs successfully updated:") % (succeeded,
1571 total)
1572 if cre.failed:
1573 # This ensures that the text gets printed before the errors.
1574 emsg(txt)
1575 else:
1576 msg(txt)
1577
1578 for pub, err in cre.failed:
1579 if isinstance(err, urllib2.HTTPError):
1580 emsg(" %s: %s - %s" % \
1581 (err.filename, err.code, err.msg))
1582 elif isinstance(err, urllib2.URLError):
1583 if err.args[0][0] == 8:
1584 emsg(" %s: %s" % \
1585 (urlparse.urlsplit(
1586 pub["origin"])[1].split(":")[0],
1587 err.args[0][1]))
1588 else:
1589 if isinstance(err.args[0], socket.timeout):
1590 emsg(" %s: %s" % \
1591 (pub["origin"], "timeout"))
1592 else:
1593 emsg(" %s: %s" % \
1594 (pub["origin"], err.args[0][1]))
1595 else:
1596 emsg(" ", err)
1597
1598 if cre.message:
1599 emsg(cre.message)
1600
1601 return succeeded
1602
1603 def publisher_refresh(img_dir, args):
1604 """Update metadata for the image's publishers."""
1605
1606 # XXX will need to show available content series for each package
1607 full_refresh = False
1608 opts, pargs = getopt.getopt(args, "", ["full"])
1609 for opt, arg in opts:
1610 if opt == "--full":
1611 full_refresh = True
1612
1613 try:
1614 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1615 get_tracker(), None, PKG_CLIENT_NAME)
1616 except api_errors.ImageNotFoundException, e:
1617 error(_("'%s' is not an install image") % e.user_dir)
1618 return 1
1619
1620 try:
1621 # The user explicitly requested this refresh, so set the
1622 # refresh to occur immediately.
1623 api_inst.refresh(full_refresh=full_refresh, immediate=True,
1624 pubs=pargs)
1625 except api_errors.PublisherError, e:
1626 error(e)
1627 error(_("'pkg publisher' will show a list of publishers."))
1628 return 1
1629 except (api_errors.PermissionsException), e:
1630 # Prepend a newline because otherwise the exception will
1631 # be printed on the same line as the spinner.
1632 error("\n" + str(e))
1633 return 1
1634 except api_errors.CatalogRefreshException, e:
1635 if display_catalog_failures(e) == 0:
1636 return 1
1637 else:
1638 return 3
1639 else:
1640 return 0
1641
1642 def publisher_set(img, img_dir, args):
1643 """pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
1644 [-O origin_url] [-m mirror to add] [-M mirror to remove]
1645 [--enable] [--disable] [--no-refresh] publisher"""
1646
1647 preferred = False
1648 ssl_key = None
1649 ssl_cert = None
1650 origin_url = None
1651 reset_uuid = False
1652 add_mirror = None
1653 remove_mirror = None
1654 refresh_catalogs = True
1655 disable = None
1656
1657 opts, pargs = getopt.getopt(args, "Pedk:c:O:M:m:",
1658 ["add-mirror=", "remove-mirror=", "no-refresh", "reset-uuid",
1659 "enable", "disable"])
1660
1661 for opt, arg in opts:
1662 if opt == "-P":
1663 preferred = True
1664 if opt == "-k":
1665 ssl_key = arg
1666 if opt == "-c":
1667 ssl_cert = arg
1668 if opt == "-O":
1669 origin_url = arg
1670 if opt == "-m" or opt == "--add-mirror":
1671 add_mirror = arg
1672 if opt == "-M" or opt == "--remove-mirror":
1673 remove_mirror = arg
1674 if opt == "--no-refresh":
1675 refresh_catalogs = False
1676 if opt == "--reset-uuid":
1677 reset_uuid = True
1678 if opt == "-e" or opt == "--enable":
1679 disable = False
1680 if opt == "-d" or opt == "--disable":
1681 disable = True
1682
1683 if len(pargs) == 0:
1684 usage(_("requires a publisher name"), cmd="set-publisher")
1685 elif len(pargs) > 1:
1686 usage( _("only one publisher name may be specified"),
1687 cmd="set-publisher",)
1688
1689 name = pargs[0]
1690
1691 if preferred and disable:
1692 usage(_("the -p and -d options may not be combined"),
1693 cmd="set-publisher")
1694
1695 try:
1696 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1697 get_tracker(), None, PKG_CLIENT_NAME)
1698 except api_errors.ImageNotFoundException, e:
1699 error(_("'%s' is not an install image") % e.user_dir,
1700 cmd="set-publisher")
1701 return 1
1702
1703 new_pub = False
1704 try:
1705 pub = api_inst.get_publisher(prefix=name, alias=name,
1706 duplicate=True)
1707 if reset_uuid:
1708 pub.reset_client_uuid()
1709 repo = pub.selected_repository
1710 except api_errors.PermissionsException, e:
1711 error(e, cmd="set-publisher")
1712 return 1
1713 except api_errors.UnknownPublisher:
1714 if not origin_url:
1715 error(_("publisher does not exist. Use -O to define "
1716 "origin URI for new publisher."),
1717 cmd="set-publisher")
1718 return 1
1719 # No pre-existing, so create a new one.
1720 repo = publisher.Repository()
1721 pub = publisher.Publisher(name, repositories=[repo])
1722 new_pub = True
1723
1724 if disable is not None:
1725 # Set disabled property only if provided.
1726 pub.disabled = disable
1727
1728 if origin_url:
1729 try:
1730 if not repo.origins:
1731 # New publisher case.
1732 repo.add_origin(origin_url)
1733 origin = repo.origins[0]
1734 else:
1735 origin = repo.origins[0]
1736 origin.uri = origin_url
1737
1738 # XXX once image configuration supports storing this
1739 # information at the uri level, ssl info should be set
1740 # here.
1741 except api_errors.PublisherError, e:
1742 error(e, cmd="set-publisher")
1743 return 1
1744
1745 if add_mirror:
1746 try:
1747 # XXX once image configuration supports storing this
1748 # information at the uri level, ssl info should be set
1749 # here.
1750 repo.add_mirror(add_mirror)
1751 except (api_errors.PublisherError,
1752 api_errors.CertificateError), e:
1753 error(e, cmd="set-publisher")
1754 return 1
1755
1756 if remove_mirror:
1757 try:
1758 repo.remove_mirror(remove_mirror)
1759 except api_errors.PublisherError, e:
1760 error(e, cmd="set-publisher")
1761 return 1
1762
1763 # None is checked for here so that a client can unset a ssl_cert or
1764 # ssl_key by using -k "" or -c "".
1765 if ssl_cert is not None or ssl_key is not None:
1766 #
1767 # In the case of zones, the ssl cert given is assumed to
1768 # be relative to the root of the image, not truly absolute.
1769 #
1770 if img.is_zone():
1771 if ssl_cert is not None:
1772 ssl_cert = os.path.abspath(
1773 img.get_root() + os.sep + ssl_cert)
1774 if ssl_key is not None:
1775 ssl_key = os.path.abspath(
1776 img.get_root() + os.sep + ssl_key)
1777
1778 # Assume the user wanted to update the ssl_cert or ssl_key
1779 # information for *all* of the currently selected
1780 # repository's origins and mirrors.
1781 try:
1782 for uri in repo.origins:
1783 if ssl_cert is not None:
1784 uri.ssl_cert = ssl_cert
1785 if ssl_key is not None:
1786 uri.ssl_key = ssl_key
1787 for uri in repo.mirrors:
1788 if ssl_cert is not None:
1789 uri.ssl_cert = ssl_cert
1790 if ssl_key is not None:
1791 uri.ssl_key = ssl_key
1792 except (api_errors.PublisherError,
1793 api_errors.CertificateError), e:
1794 error(e, cmd="set-publisher")
1795 return 1
1796
1797 try:
1798 if new_pub:
1799 api_inst.add_publisher(pub,
1800 refresh_allowed=refresh_catalogs)
1801 else:
1802 api_inst.update_publisher(pub,
1803 refresh_allowed=refresh_catalogs)
1804 except api_errors.CatalogRefreshException, e:
1805 text = "Could not refresh the catalog for %s"
1806 error(_(text) % pub)
1807 return 1
1808 except api_errors.InvalidDepotResponseException, e:
1809 error(_("The origin URIs for '%(pubname)s' do not appear to "
1810 "point to a valid pkg server.\nPlease check the server's "
1811 "address and client's network configuration."
1812 "\nAdditional details:\n\n%(details)s") %
1813 { "pubname": pub.prefix, "details": e })
1814 return 1
1815 except api_errors.PermissionsException, e:
1816 # Prepend a newline because otherwise the exception will
1817 # be printed on the same line as the spinner.
1818 error("\n" + str(e))
1819 return 1
1820
1821 if preferred:
1822 api_inst.set_preferred_publisher(prefix=pub.prefix)
1823
1824 return 0
1825
1826 def publisher_unset(img_dir, args):
1827 """pkg unset-publisher publisher ..."""
1828
1829 if len(args) == 0:
1830 usage()
1831
1832 try:
1833 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1834 get_tracker(), None, PKG_CLIENT_NAME)
1835 except api_errors.ImageNotFoundException, e:
1836 error(_("'%s' is not an install image") % e.user_dir)
1837 return 1
1838
1839 errors = []
1840 for name in args:
1841 try:
1842 api_inst.remove_publisher(prefix=name, alias=name)
1843 except (api_errors.PermissionsException,
1844 api_errors.PublisherError), e:
1845 errors.append((name, e))
1846
1847 retcode = 0
1848 if errors:
1849 if len(errors) == len(args):
1850 # If the operation failed for every provided publisher
1851 # prefix or alias, complete failure occurred.
1852 retcode = 1
1853 else:
1854 # If the operation failed for only some of the provided
1855 # publisher prefixes or aliases, then partial failure
1856 # occurred.
1857 retcode = 3
1858
1859 txt = ""
1860 for name, err in errors:
1861 txt += "\n"
1862 txt += _("Removal failed for '%(pub)s': %(msg)s") % {
1863 "pub": name, "msg": err }
1864 txt += "\n"
1865 error(txt, cmd="unset-publisher")
1866
1867 return retcode
1868
1869 def publisher_list(img_dir, args):
1870 """pkg publishers"""
1871 omit_headers = False
1872 preferred_only = False
1873 inc_disabled = False
1874
1875 opts, pargs = getopt.getopt(args, "HPa")
1876 for opt, arg in opts:
1877 if opt == "-H":
1878 omit_headers = True
1879 if opt == "-P":
1880 preferred_only = True
1881 if opt == "-a":
1882 inc_disabled = True
1883
1884 progresstracker = get_tracker(True)
1885
1886 try:
1887 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1888 progresstracker, None, PKG_CLIENT_NAME)
1889 except api_errors.ImageNotFoundException, e:
1890 error(_("'%s' is not an install image") % e.user_dir)
1891 return 1
1892
1893 cert_cache = {}
1894 def get_cert_info(ssl_cert):
1895 if not ssl_cert:
1896 return None
1897 if ssl_cert not in cert_cache:
1898 c = cert_cache[ssl_cert] = {}
1899 errors = c["errors"] = []
1900 times = c["info"] = {
1901 "effective": "",
1902 "expiration": "",
1903 }
1904
1905 try:
1906 cert = misc.validate_ssl_cert(ssl_cert)
1907 except (EnvironmentError,
1908 api_errors.CertificateError,
1909 api_errors.PermissionsException), e:
1910 # If the cert information can't be retrieved,
1911 # add the errors to a list and continue on.
1912 errors.append(e)
1913 c["valid"] = False
1914 else:
1915 nb = cert.get_notBefore()
1916 t = time.strptime(nb, "%Y%m%d%H%M%SZ")
1917 nb = datetime.datetime.utcfromtimestamp(
1918 calendar.timegm(t))
1919 times["effective"] = nb.strftime("%c")
1920
1921 na = cert.get_notAfter()
1922 t = time.strptime(na, "%Y%m%d%H%M%SZ")
1923 na = datetime.datetime.utcfromtimestamp(
1924 calendar.timegm(t))
1925 times["expiration"] = na.strftime("%c")
1926 c["valid"] = True
1927
1928 return cert_cache[ssl_cert]
1929
1930 retcode = 0
1931 if len(pargs) == 0:
1932 fmt = "%-24s %-12s %-8s %-8s %s"
1933 if not omit_headers:
1934 msg(fmt % (_("PUBLISHER"), "", _("TYPE"), _("STATUS"),
1935 _("URI")))
1936
1937 pref_pub = api_inst.get_preferred_publisher()
1938 if preferred_only:
1939 pubs = [pref_pub]
1940 else:
1941 pubs = [
1942 p for p in api_inst.get_publishers()
1943 if inc_disabled or not p.disabled
1944 ]
1945
1946 for p in pubs:
1947 pfx = p.prefix
1948 pstatus = ""
1949 if not preferred_only and p == pref_pub:
1950 pstatus = _("(preferred)")
1951 if p.disabled:
1952 pstatus = _("(disabled)")
1953
1954 # Only show the selected repository's information in
1955 # summary view.
1956 r = p.selected_repository
1957 for uri in r.origins:
1958 # XXX get the real origin status
1959 msg(fmt % (pfx, pstatus, _("origin"), "online",
1960 uri))
1961 for uri in r.mirrors:
1962 # XXX get the real mirror status
1963 msg(fmt % (pfx, pstatus, _("mirror"), "online",
1964 uri))
1965 else:
1966 def display_ssl_info(uri):
1967 retcode = 0
1968 c = get_cert_info(uri.ssl_cert)
1969 msg(_(" SSL Key:"), uri.ssl_key)
1970 msg(_(" SSL Cert:"), uri.ssl_cert)
1971
1972 if not c:
1973 return retcode
1974
1975 if c["errors"]:
1976 retcode = 1
1977
1978 for e in c["errors"]:
1979 emsg("\n" + str(e) + "\n")
1980
1981 if c["valid"]:
1982 msg(_(" Cert. Effective Date:"),
1983 c["info"]["effective"])
1984 msg(_("Cert. Expiration Date:"),
1985 c["info"]["expiration"])
1986 return retcode
1987
1988 def display_repository(r):
1989 retcode = 0
1990 for uri in r.origins:
1991 msg(_(" Origin URI:"), uri)
1992 rval = display_ssl_info(uri)
1993 if rval == 1:
1994 retcode = 3
1995
1996 for uri in r.mirrors:
1997 msg(_(" Mirror URI:"), uri)
1998 rval = display_ssl_info(uri)
1999 if rval == 1:
2000 retcode = 3
2001 return retcode
2002
2003 for name in pargs:
2004 # detailed print
2005 pub = api_inst.get_publisher(prefix=name, alias=name)
2006 dt = api_inst.get_publisher_last_update_time(pub.prefix)
2007 if dt:
2008 dt = dt.strftime("%c")
2009
2010 msg("")
2011 msg(_(" Publisher:"), pub.prefix)
2012 msg(_(" Alias:"), pub.alias)
2013
2014 for r in pub.repositories:
2015 rval = display_repository(r)
2016 if rval != 0:
2017 # There was an error in displaying some
2018 # of the information about a repository.
2019 # However, continue on.
2020 retcode = rval
2021
2022 msg(_(" Client UUID:"), pub.client_uuid)
2023 msg(_(" Catalog Updated:"), dt)
2024 if pub.disabled:
2025 msg(_(" Enabled:"), _("No"))
2026 else:
2027 msg(_(" Enabled:"), _("Yes"))
2028 return retcode
2029
2030 def property_set(img, args):
2031 """pkg set-property propname propvalue"""
2032
2033 # ensure no options are passed in
2034 opts, pargs = getopt.getopt(args, "")
2035 try:
2036 propname, propvalue = pargs
2037 except ValueError:
2038 usage(_("set-property: requires a property name and value"))
2039
2040 if propname == "preferred-publisher":
2041 error(_("set-property: set-publisher must be used to change "
2042 "the preferred publisher"))
2043 return 1
2044
2045 try:
2046 img.set_property(propname, propvalue)
2047 except api_errors.PermissionsException, e:
2048 # Prepend a newline because otherwise the exception will
2049 # be printed on the same line as the spinner.
2050 error("\nset-property failed:\n" + str(e))
2051 return 1
2052
2053 return 0
2054
2055 def property_unset(img, args):
2056 """pkg unset-property propname ..."""
2057
2058 # is this an existing property in our image?
2059 # if so, delete it
2060 # if not, error
2061
2062 # ensure no options are passed in
2063 opts, pargs = getopt.getopt(args, "")
2064 if not pargs:
2065 usage(_("unset-property: requires at least one property name"))
2066
2067 for p in pargs:
2068 if p == "preferred-publisher":
2069 error(_("unset-property: set-publisher must be used to "
2070 "change the preferred publisher"))
2071 return 1
2072
2073 try:
2074 img.delete_property(p)
2075 except KeyError:
2076 error(_("unset-property: no such property: %s") % p)
2077 return 1
2078 except api_errors.PermissionsException, e:
2079 # Prepend a newline because otherwise the exception
2080 # will be printed on the same line as the spinner.
2081 error("\n" + str(e))
2082 return 1
2083
2084 return 0
2085
2086 def property_list(img, args):
2087 """pkg property [-H] [propname ...]"""
2088 omit_headers = False
2089
2090 opts, pargs = getopt.getopt(args, "H")
2091 for opt, arg in opts:
2092 if opt == "-H":
2093 omit_headers = True
2094
2095 for p in pargs:
2096 if not img.has_property(p):
2097 error(_("property: no such property: %s") % p)
2098 return 1
2099
2100 if not pargs:
2101 pargs = list(img.properties())
2102
2103 width = max(max([len(p) for p in pargs]), 8)
2104 fmt = "%%-%ss %%s" % width
2105 if not omit_headers:
2106 msg(fmt % ("PROPERTY", "VALUE"))
2107
2108 for p in pargs:
2109 msg(fmt % (p, img.get_property(p)))
2110
2111 return 0
2112
2113 def image_create(img, args):
2114 """Create an image of the requested kind, at the given path. Load
2115 catalog for initial publisher for convenience.
2116
2117 At present, it is legitimate for a user image to specify that it will be
2118 deployed in a zone. An easy example would be a program with an optional
2119 component that consumes global zone-only information, such as various
2120 kernel statistics or device information."""
2121
2122 imgtype = image.IMG_USER
2123 is_zone = False
2124 ssl_key = None
2125 ssl_cert = None
2126 pub_name = None
2127 pub_url = None
2128 refresh_catalogs = True
2129 force = False
2130 variants = {}
2131
2132 opts, pargs = getopt.getopt(args, "fFPUza:p:k:c:",
2133 ["force", "full", "partial", "user", "zone", "authority=",
2134 "publisher=", "no-refresh", "variant="])
2135
2136 for opt, arg in opts:
2137 if opt == "-f" or opt == "--force":
2138 force = True
2139 if opt == "-F" or opt == "--full":
2140 imgtype = imgtypes.IMG_ENTIRE
2141 if opt == "-P" or opt == "--partial":
2142 imgtype = imgtypes.IMG_PARTIAL
2143 if opt == "-U" or opt == "--user":
2144 imgtype = imgtypes.IMG_USER
2145 if opt == "-z" or opt == "--zone":
2146 is_zone = True
2147 imgtype = image.IMG_ENTIRE
2148 if opt == "--no-refresh":
2149 refresh_catalogs = False
2150 if opt == "-k":
2151 ssl_key = arg
2152 if opt == "-c":
2153 ssl_cert = arg
2154
2155 # -a is deprecated and will be removed at a future date.
2156 if opt in ("-a", "-p", "--publisher"):
2157 try:
2158 pub_name, pub_url = arg.split("=", 1)
2159 except ValueError:
2160 usage(_("image-create requires publisher "
2161 "argument to be of the form "
2162 "'<prefix>=<url>'."))
2163 if opt == "--variant":
2164 try:
2165 v_name, v_value = arg.split("=", 1)
2166 if not v_name.startswith("variant."):
2167 v_name = "variant.%s" % v_name
2168 except ValueError:
2169 usage(_("image-create requires variant "
2170 "arguments to be of the form "
2171 "'<name>=<value>'."))
2172 variants[v_name] = v_value
2173
2174 if len(pargs) != 1:
2175 usage(_("image-create requires a single image directory path"))
2176 image_dir = pargs[0]
2177
2178 if ssl_key:
2179 # When creating zones, the path is image-root-relative.
2180 if is_zone:
2181 ssl_key = os.path.normpath(image_dir + os.sep + \
2182 ssl_key)
2183 else:
2184 ssl_key = os.path.abspath(ssl_key)
2185 if not os.path.exists(ssl_key):
2186 msg(_("pkg: set-publisher: SSL key file '%s' does " \
2187 "not exist") % ssl_key)
2188 return 1
2189
2190 if ssl_cert:
2191 # When creating zones, the path is image-root-relative.
2192 if is_zone:
2193 ssl_cert = os.path.normpath(image_dir + os.sep + \
2194 ssl_cert)
2195 else:
2196 ssl_cert = os.path.abspath(ssl_cert)
2197 if not os.path.exists(ssl_cert):
2198 msg(_("pkg: set-publisher: SSL key cert '%s' does " \
2199 "not exist") % ssl_cert)
2200 return 1
2201
2202 if not pub_name and not pub_url:
2203 usage(_("image-create requires a publisher argument"))
2204
2205 if not pub_name or not pub_url:
2206 usage(_("image-create requires publisher argument to be of "
2207 "the form '<prefix>=<url>'."))
2208
2209 if pub_name.startswith(fmri.PREF_PUB_PFX):
2210 error(_("image-create requires that a prefix not match: %s"
2211 % fmri.PREF_PUB_PFX))
2212 return 1
2213
2214 if not misc.valid_pub_prefix(pub_name):
2215 error(_("image-create: publisher prefix has invalid " \
2216 "characters"))
2217 return 1
2218
2219 # Bail if there is already an image there
2220 if img.image_type(image_dir) != None and not force:
2221 error(_("there is already an image at: %s") % image_dir)
2222 error(_("To override, use the -f (force) option."))
2223 return 1
2224
2225 # Bail if the directory exists but isn't empty
2226 if os.path.exists(image_dir) and \
2227 len(os.listdir(image_dir)) > 0 and not force:
2228 error(_("Non-empty directory: %s") % image_dir)
2229 error(_("To override, use the -f (force) option."))
2230 return 1
2231
2232 try:
2233 img.set_attrs(imgtype, image_dir, is_zone, pub_name, pub_url,
2234 ssl_key=ssl_key, ssl_cert=ssl_cert, variants=variants,
2235 refresh_allowed=refresh_catalogs, progtrack=get_tracker())
2236 except OSError, e:
2237 # Ensure messages are displayed after the spinner.
2238 emsg("\n")
2239 error(_("cannot create image at %(image_dir)s: %(reason)s") %
2240 { "image_dir": image_dir, "reason": e.args[1] })
2241 return 1
2242 except api_errors.PermissionsException, e:
2243 # Ensure messages are displayed after the spinner.
2244 emsg("")
2245 error(e, cmd="image-create")
2246 return 1
2247 except api_errors.InvalidDepotResponseException, e:
2248 # Ensure messages are displayed after the spinner.
2249 emsg("\n")
2250 error(_("The URI '%(pub_url)s' does not appear to point to a "
2251 "valid pkg server.\nPlease check the server's "
2252 "address and client's network configuration."
2253 "\nAdditional details:\n\n%(error)s") %
2254 { "pub_url": pub_url, "error": e },
2255 cmd="image-create")
2256 print_proxy_config()
2257 return 1
2258 except api_errors.CatalogRefreshException, cre:
2259 # Ensure messages are displayed after the spinner.
2260 error("", cmd="image-create")
2261 if display_catalog_failures(cre) == 0:
2262 return 1
2263 else:
2264 return 3
2265 return 0
2266
2267
2268 def rebuild_index(img_dir, pargs):
2269 """pkg rebuild-index
2270
2271 Forcibly rebuild the search indexes. Will remove existing indexes
2272 and build new ones from scratch."""
2273 quiet = False
2274
2275 if pargs:
2276 usage(_("rebuild-index: command does not take operands " \
2277 "('%s')") % " ".join(pargs))
2278 try:
2279 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
2280 get_tracker(quiet), None, PKG_CLIENT_NAME)
2281 except api_errors.ImageNotFoundException, e:
2282 error(_("'%s' is not an install image") % e.user_dir)
2283 return 1
2284
2285 try:
2286 api_inst.rebuild_search_index()
2287 except api_errors.CorruptedIndexException:
2288 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
2289 return 1
2290 except api_errors.ProblematicPermissionsIndexException, e:
2291 error(str(e) + PROBLEMATIC_PERMISSIONS_ERROR_MESSAGE)
2292 return 1
2293 except api_errors.MainDictParsingException, e:
2294 error(str(e))
2295 return 1
2296 else:
2297 return 0
2298
2299 def history_list(img, args):
2300 """Display history about the current image.
2301 """
2302
2303 omit_headers = False
2304 long_format = False
2305
2306 opts, pargs = getopt.getopt(args, "Hl")
2307 for opt, arg in opts:
2308 if opt == "-H":
2309 omit_headers = True
2310 elif opt == "-l":
2311 long_format = True
2312
2313 if omit_headers and long_format:
2314 usage(_("history: -H and -l may not be combined"))
2315
2316 if not long_format:
2317 if not omit_headers:
2318 msg("%-19s %-25s %-15s %s" % (_("TIME"),
2319 _("OPERATION"), _("CLIENT"), _("OUTCOME")))
2320
2321 if not os.path.exists(img.history.path):
2322 # Nothing to display.
2323 return 0
2324
2325 for entry in sorted(os.listdir(img.history.path)):
2326 # Load the history entry.
2327 try:
2328 he = history.History(root_dir=img.history.root_dir,
2329 filename=entry)
2330 except api_errors.PermissionsException, e:
2331 error(e, cmd="history")
2332 return 1
2333 except history.HistoryLoadException, e:
2334 if e.parse_failure:
2335 # Ignore corrupt entries.
2336 continue
2337 raise
2338
2339 # Retrieve and format some of the data shared between each
2340 # output format.
2341 start_time = misc.timestamp_to_time(
2342 he.operation_start_time)
2343 start_time = datetime.datetime.fromtimestamp(
2344 start_time).isoformat()
2345
2346 res = he.operation_result
2347 if len(res) > 1:
2348 outcome = "%s (%s)" % (_(res[0]), _(res[1]))
2349 else:
2350 outcome = _(res[0])
2351
2352 if long_format:
2353 data = []
2354 data.append(("Operation", he.operation_name))
2355
2356 data.append(("Outcome", outcome))
2357 data.append(("Client", he.client_name))
2358 data.append(("Version", he.client_version))
2359
2360 data.append(("User", "%s (%s)" % \
2361 (he.operation_username, he.operation_userid)))
2362
2363 data.append(("Start Time", start_time))
2364
2365 end_time = misc.timestamp_to_time(
2366 he.operation_end_time)
2367 end_time = datetime.datetime.fromtimestamp(
2368 end_time).isoformat()
2369 data.append(("End Time", end_time))
2370
2371 data.append(("Command", " ".join(he.client_args)))
2372
2373 state = he.operation_start_state
2374 if state:
2375 data.append(("Start State", "\n" + state))
2376
2377 state = he.operation_end_state
2378 if state:
2379 data.append(("End State", "\n" + state))
2380
2381 errors = "\n".join(he.operation_errors)
2382 if errors:
2383 data.append(("Errors", "\n" + errors))
2384
2385 for field, value in data:
2386 msg("%15s: %s" % (_(field), value))
2387
2388 # Separate log entries with a blank line.
2389 msg("")
2390 else:
2391 msg("%-19s %-25s %-15s %s" % (start_time,
2392 he.operation_name, he.client_name, outcome))
2393
2394 return 0
2395
2396 def print_proxy_config():
2397 """If the user has configured http_proxy or https_proxy in the
2398 environment, print out the values. Some transport errors are
2399 not debuggable without this information handy."""
2400
2401 http_proxy = os.environ.get("http_proxy", None)
2402 https_proxy = os.environ.get("https_proxy", None)
2403
2404 if not http_proxy and not https_proxy:
2405 return
2406
2407 emsg(_("\nThe following proxy configuration is set in the"
2408 " environment:\n"))
2409 if http_proxy:
2410 emsg(_("http_proxy: %s\n") % http_proxy)
2411 if https_proxy:
2412 emsg(_("https_proxy: %s\n") % https_proxy)
2413
2414
2415 # To allow exception handler access to the image.
2416 __img = None
2417
2418 def main_func():
2419 global_settings.client_name = PKG_CLIENT_NAME
2420
2421 global __img
2422 __img = img = image.Image()
2423
2424 misc.setlocale(locale.LC_ALL, "", error)
2425 gettext.install("pkg", "/usr/share/locale")
2426
2427 try:
2428 opts, pargs = getopt.getopt(sys.argv[1:], "R:D:?",
2429 ["debug=", "help"])
2430 except getopt.GetoptError, e:
2431 usage(_("illegal global option -- %s") % e.opt)
2432
2433 show_usage = False
2434 for opt, arg in opts:
2435 if opt == "-D" or opt == "--debug":
2436 try:
2437 key, value = arg.split("=", 1)
2438 except (AttributeError, ValueError):
2439 usage(_("%(opt)s takes argument of form "
2440 "name=value, not %(arg)s") % { "opt": opt,
2441 "arg": arg })
2442 DebugValues.set_value(key, value)
2443 elif opt == "-R":
2444 mydir = arg
2445 elif opt in ("--help", "-?"):
2446 show_usage = True
2447
2448 subcommand = None
2449 if pargs:
2450 subcommand = pargs.pop(0)
2451 if subcommand == "help":
2452 show_usage = True
2453
2454 if show_usage:
2455 usage(retcode=0)
2456 elif not subcommand:
2457 usage()
2458
2459 # Override default PKG_TIMEOUT_MAX and PKG_CLIENT_TIMEOUT
2460 # if a value has been specified in the environment.
2461 global_settings.PKG_TIMEOUT_MAX = int(os.environ.get("PKG_TIMEOUT_MAX",
2462 global_settings.PKG_TIMEOUT_MAX))
2463
2464 global_settings.PKG_CLIENT_TIMEOUT = int(os.environ.get(
2465 "PKG_CLIENT_TIMEOUT", global_settings.PKG_CLIENT_TIMEOUT))
2466
2467 # This call only affects sockets created by Python. The transport
2468 # framework must set the timeout value internally.
2469 socket.setdefaulttimeout(global_settings.PKG_TIMEOUT_MAX) # in seconds
2470
2471 if subcommand == "image-create":
2472 if "mydir" in locals():
2473 usage(_("-R not allowed for %s subcommand") %
2474 subcommand)
2475 try:
2476 ret = image_create(img, pargs)
2477 except getopt.GetoptError, e:
2478 usage(_("illegal %s option -- %s") % \
2479 (subcommand, e.opt))
2480 return ret
2481 elif subcommand == "version":
2482 if "mydir" in locals():
2483 usage(_("-R not allowed for %s subcommand") %
2484 subcommand)
2485 if pargs:
2486 usage(_("version: command does not take operands " \
2487 "('%s')") % " ".join(pargs))
2488 msg(pkg.VERSION)
2489 return 0
2490
2491 provided_image_dir = True
2492 pkg_image_used = False
2493
2494 if "mydir" not in locals():
2495 try:
2496 mydir = os.environ["PKG_IMAGE"]
2497 pkg_image_used = True
2498 except KeyError:
2499 try:
2500 provided_image_dir = False
2501 mydir = os.getcwd()
2502 except OSError, e:
2503 try:
2504 mydir = os.environ["PWD"]
2505 if not mydir or mydir[0] != "/":
2506 mydir = None
2507 except KeyError:
2508 mydir = None
2509
2510 if mydir == None:
2511 error(_("Could not find image. Use the -R option or set "
2512 "$PKG_IMAGE to point\nto an image, or change the working "
2513 "directory to one inside the image."))
2514 return 1
2515
2516 try:
2517 img.find_root(mydir, provided_image_dir)
2518 except api_errors.ImageNotFoundException, e:
2519 if e.user_specified:
2520 m = "No image rooted at '%s'"
2521 if pkg_image_used:
2522 m += " (set by $PKG_IMAGE)"
2523 error(_(m) % e.user_dir)
2524 else:
2525 error(_("No image found."))
2526 return 1
2527
2528 try:
2529 img.load_config()
2530 except api_errors.ApiException, e:
2531 error(_("client configuration error: %s") % e)
2532 return 1
2533
2534 try:
2535 if subcommand == "refresh":
2536 return publisher_refresh(mydir, pargs)
2537 elif subcommand == "list":
2538 return list_inventory(img, pargs)
2539 elif subcommand == "image-update":
2540 return image_update(mydir, pargs)
2541 elif subcommand == "install":
2542 return install(mydir, pargs)
2543 elif subcommand == "uninstall":
2544 return uninstall(mydir, pargs)
2545 elif subcommand == "freeze":
2546 return freeze(img, pargs)
2547 elif subcommand == "unfreeze":
2548 return unfreeze(img, pargs)
2549 elif subcommand == "search":
2550 return search(mydir, pargs)
2551 elif subcommand == "info":
2552 return info(mydir, pargs)
2553 elif subcommand == "contents":
2554 return list_contents(img, pargs)
2555 elif subcommand == "fix":
2556 return fix_image(img, pargs)
2557 elif subcommand == "verify":
2558 return verify_image(img, pargs)
2559 elif subcommand in ("set-authority", "set-publisher"):
2560 return publisher_set(img, mydir, pargs)
2561 elif subcommand in ("unset-authority", "unset-publisher"):
2562 return publisher_unset(mydir, pargs)
2563 elif subcommand in ("authority", "publisher"):
2564 return publisher_list(mydir, pargs)
2565 elif subcommand == "set-property":
2566 return property_set(img, pargs)
2567 elif subcommand == "unset-property":
2568 return property_unset(img, pargs)
2569 elif subcommand == "property":
2570 return property_list(img, pargs)
2571 elif subcommand == "history":
2572 return history_list(img, pargs)
2573 elif subcommand == "purge-history":
2574 ret_code = img.history.purge()
2575 if ret_code == 0:
2576 msg(_("History purged."))
2577 return ret_code
2578 elif subcommand == "rebuild-index":
2579 return rebuild_index(mydir, pargs)
2580 else:
2581 usage(_("unknown subcommand '%s'") % subcommand)
2582
2583 except getopt.GetoptError, e:
2584 usage(_("illegal %(cmd)s option -- %(error)s") %
2585 { "cmd": subcommand, "error": e.opt })
2586
2587
2588 #
2589 # Establish a specific exit status which means: "python barfed an exception"
2590 # so that we can more easily detect these in testing of the CLI commands.
2591 #
2592 if __name__ == "__main__":
2593 try:
2594 # Out of memory errors can be raised as EnvironmentErrors with
2595 # an errno of ENOMEM, so in order to handle those exceptions
2596 # with other errnos, we nest this try block and have the outer
2597 # one handle the other instances.
2598 try:
2599 __ret = main_func()
2600 except (MemoryError, EnvironmentError), __e:
2601 if isinstance(__e, EnvironmentError) and \
2602 __e.errno != errno.ENOMEM:
2603 raise
2604 if __img:
2605 __img.history.abort(RESULT_FAILED_OUTOFMEMORY)
2606 error("\n" + misc.out_of_memory())
2607 __ret = 1
2608 except SystemExit, __e:
2609 if __img:
2610 __img.history.abort(RESULT_FAILED_UNKNOWN)
2611 raise __e
2612 except (PipeError, KeyboardInterrupt):
2613 if __img:
2614 __img.history.abort(RESULT_CANCELED)
2615 # We don't want to display any messages here to prevent
2616 # possible further broken pipe (EPIPE) errors.
2617 __ret = 1
2618 except api_errors.CertificateError, __e:
2619 if __img:
2620 __img.history.abort(RESULT_FAILED_CONFIGURATION)
2621 error(__e)
2622 __ret = 1
2623 except api_errors.PublisherError, __e:
2624 if __img:
2625 __img.history.abort(RESULT_FAILED_BAD_REQUEST)
2626 error(__e)
2627 __ret = 1
2628 except api_errors.TransportError, __e:
2629 if __img:
2630 __img.history.abort(RESULT_FAILED_TRANSPORT)
2631 emsg(_("\nErrors were encountered while attempting to retrieve"
2632 " package or file data for\nthe requested operation."))
2633 emsg(_("Details follow:\n\n%s") % __e)
2634 print_proxy_config()
2635 __ret = 1
2636 except api_errors.InvalidDepotResponseException, __e:
2637 if __img:
2638 __img.history.abort(RESULT_FAILED_TRANSPORT)
2639 emsg(_("\nUnable to contact a valid package depot. "
2640 "This may be due to a problem with the server, "
2641 "network misconfiguration, or an incorrect pkg client "
2642 "configuration. Please check your network settings and "
2643 "attempt to contact the server using a web browser."))
2644 emsg(_("\nAdditional details:\n\n%s") % __e)
2645 print_proxy_config()
2646 __ret = 1
2647 except history.HistoryLoadException, __e:
2648 # Since a history related error occurred, discard all
2649 # information about the current operation(s) in progress.
2650 if __img:
2651 __img.history.clear()
2652 error(_("An error was encountered while attempting to load "
2653 "history information\nabout past client operations."))
2654 error(__e)
2655 __ret = 1
2656 except history.HistoryStoreException, __e:
2657 # Since a history related error occurred, discard all
2658 # information about the current operation(s) in progress.
2659 if __img:
2660 __img.history.clear()
2661 error(_("An error was encountered while attempting to store "
2662 "information about the\ncurrent operation in client "
2663 "history."))
2664 error(__e)
2665 __ret = 1
2666 except history.HistoryPurgeException, __e:
2667 # Since a history related error occurred, discard all
2668 # information about the current operation(s) in progress.
2669 if __img:
2670 __img.history.clear()
2671 error(_("An error was encountered while attempting to purge "
2672 "client history."))
2673 error(__e)
2674 __ret = 1
2675 except api_errors.VersionException, __e:
2676 if __img:
2677 __img.history.abort(RESULT_FAILED_UNKNOWN)
2678 error(_("The pkg command appears out of sync with the "
2679 "libraries provided \nby SUNWipkg. The client version is "
2680 "%(client)s while the library API version is %(api)s") %
2681 {'client': __e.received_version,
2682 'api': __e.expected_version
2683 })
2684 __ret = 1
2685 except:
2686 if __img:
2687 __img.history.abort(RESULT_FAILED_UNKNOWN)
2688 traceback.print_exc()
2689 error(
2690 _("\n\nThis is an internal error. Please let the "
2691 "developers know about this\nproblem by filing a bug at "
2692 "http://defect.opensolaris.org and including the\nabove "
2693 "traceback and this message. The version of pkg(5) is "
2694 "'%s'.") % pkg.VERSION)
2695 __ret = 99
2696
2697 sys.exit(__ret)