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