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.
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.
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]
24 # Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
40 import pkg
.catalog
as catalog
41 import pkg
.client
.progress
as progress
43 import pkg
.manifest
as manifest
44 import pkg
.client
.api_errors
as apx
45 import pkg
.client
.pkgdefs
as pkgdefs
46 import pkg
.client
.publisher
as publisher
47 import pkg
.client
.transport
.transport
as transport
48 import pkg
.misc
as misc
50 import pkg
.pkgsubprocess
as subprocess
51 import pkg
.publish
.transaction
as trans
52 import pkg
.server
.repository
as sr
53 import pkg
.version
as version
55 from pkg
.client
import global_settings
56 from pkg
.misc
import emsg
, get_pkg_otw_size
, msg
, PipeError
57 from pkg
.client
.debugvalues
import DebugValues
63 download_start
= False
73 """Emit an error message prefixed by the command name """
75 # If we get passed something like an Exception, we can convert
76 # it down to a string.
79 # If the message starts with whitespace, assume that it should come
80 # *before* the command-name prefix.
81 text_nows
= text
.lstrip()
82 ws
= text
[:len(text
) - len(text_nows
)]
84 # This has to be a constant value as we can't reliably get our actual
85 # program name on all platforms.
86 emsg(ws
+ "pkgrecv: " + text_nows
)
88 def usage(usage_error
=None, retcode
=2):
89 """Emit a usage message and optionally prefix it with a more specific
90 error message. Causes program to exit."""
97 pkgrecv [-aknrv] [-s src_uri] [-d (path|dest_uri)] [-c cache_dir]
98 [-m match] [--raw] [--key src_key --cert src_cert]
99 [--dkey dest_key --dcert dest_cert]
101 pkgrecv [-s src_repo_uri] --newest
102 pkgrecv [-nv] [-s src_repo_uri] [-d path] [-p publisher ...]
103 [--key src_key --cert src_cert] --clone
106 -a Store the retrieved package data in a pkg(5) archive
107 at the location specified by -d. The file may not
108 already exist, and this option may only be used with
109 filesystem-based destinations.
111 -c cache_dir The path to a directory that will be used to cache
112 downloaded content. If one is not supplied, the
113 client will automatically pick a cache directory.
114 In the case where a download is interrupted, and a
115 cache directory was automatically chosen, use this
116 option to resume the download.
118 -d path_or_uri The filesystem path or URI of the target repository to
119 republish packages to. The target must already exist.
120 New repositories can be created using pkgrepo(1).
122 -h Display this usage message.
124 -k Keep the retrieved package content compressed, ignored
125 when republishing. Should not be used with pkgsend.
127 -m match Controls matching behaviour using the following values:
128 all-timestamps (default)
129 includes all matching timestamps (implies
132 includes all matching versions
134 includes only the latest version of each package
136 -n Perform a trial run with no changes made.
138 -v Display verbose output.
140 -p publisher Only clone the given publisher. Can be specified
141 multiple times. Only valid with --clone.
143 -r Recursively evaluates all dependencies for the provided
144 list of packages and adds them to the list.
146 -s src_repo_uri A URI representing the location of a pkg(5)
147 repository to retrieve package data from.
149 --clone Make an exact copy of the source repository. By default,
150 the clone operation will only succeed if publishers in
151 the source repository are also present in the
152 destination. By using -p, the operation can be limited
153 to specific publishers which will be added to the
154 destination repository if not already present.
155 Packages in the destination repository which are not in
156 the source will be removed.
157 Cloning will leave the destination repository altered in
160 --newest List the most recent versions of the packages available
161 from the specified repository and exit. (All other
162 options except -s will be ignored.)
164 --raw Retrieve and store the raw package data in a set of
165 directory structures by stem and version at the location
166 specified by -d. May only be used with filesystem-
167 based destinations. This can be used with pkgsend(1)
168 include to conveniently modify and republish packages,
169 perhaps by correcting file contents or providing
170 additional package metadata.
172 --key src_key Specify a client SSL key file to use for pkg retrieval.
174 --cert src_cert Specify a client SSL certificate file to use for pkg
177 --dkey dest_key Specify a client SSL key file to use for pkg
180 --dcert dest_cert Specify a client SSL certificate file to use for pkg
184 PKG_DEST Destination directory or URI
185 PKG_SRC Source URI or path"""))
188 def cleanup(caller_error
=False):
189 """To be called at program finish."""
192 # If the cache_dir is in the list of directories that should
193 # be cleaned up, but we're exiting with an error, then preserve
194 # the directory so downloads may be resumed.
195 if d
== cache_dir
and caller_error
and download_start
:
196 error(_("\n\nCached files were preserved in the "
197 "following directory:\n\t{0}\nUse pkgrecv -c "
198 "to resume the interrupted download.").format(
201 shutil
.rmtree(d
, ignore_errors
=True)
203 if caller_error
and dest_xport
and targ_pub
and not archive
:
205 dest_xport
.publish_refresh_packages(targ_pub
)
206 except apx
.TransportError
:
207 # If this fails, ignore it as this was a last ditch
211 def abort(err
=None, retcode
=1):
212 """To be called when a fatal error is encountered."""
215 # Clear any possible output first.
219 cleanup(caller_error
=True)
225 progress
.FancyUNIXProgressTracker()
226 except progress
.ProgressTrackerException
:
227 progresstracker
= progress
.CommandLineProgressTracker()
228 progresstracker
.set_major_phase(progresstracker
.PHASE_UTILITY
)
229 return progresstracker
231 def get_manifest(pfmri
, xport_cfg
, contents
=False):
234 pkgdir
= xport_cfg
.get_pkg_dir(pfmri
)
235 mpath
= xport_cfg
.get_pkg_pathname(pfmri
)
237 if not os
.path
.exists(mpath
):
238 m
= xport
.get_manifest(pfmri
)
240 # A FactoredManifest is used here to reduce peak memory
241 # usage (notably when -r was specified).
243 m
= manifest
.FactoredManifest(pfmri
, pkgdir
)
245 abort(err
=_("Unable to parse manifest '{mpath}' for "
246 "package '{pfmri}'").format(**locals()))
249 return m
.tostr_unsorted()
252 def expand_fmri(pfmri
, constraint
=version
.CONSTRAINT_AUTO
):
253 """Find matching fmri using CONSTRAINT_AUTO cache for performance.
254 Returns None if no matching fmri is found."""
255 if isinstance(pfmri
, str):
256 pfmri
= pkg
.fmri
.PkgFmri(pfmri
)
258 # Iterate in reverse so newest version is evaluated first.
259 versions
= [e
for e
in src_cat
.fmris_by_version(pfmri
.pkg_name
)]
260 for v
, fmris
in reversed(versions
):
262 if not pfmri
.version
or \
263 f
.version
.is_successor(pfmri
.version
, constraint
):
267 def get_dependencies(fmri_list
, xport_cfg
, tracker
):
269 old_limit
= sys
.getrecursionlimit()
270 # The user may be recursing 'entire' or 'redistributable'.
271 sys
.setrecursionlimit(3000)
275 _get_dependencies(s
, f
, xport_cfg
, tracker
)
277 # Restore the previous default.
278 sys
.setrecursionlimit(old_limit
)
282 def _get_dependencies(s
, pfmri
, xport_cfg
, tracker
):
283 """Expand all dependencies."""
285 # tracker.evaluate_progress(pkgfmri=pfmri)
288 m
= get_manifest(pfmri
, xport_cfg
)
289 for a
in m
.gen_actions_by_type("depend"):
290 for fmri_str
in a
.attrlist("fmri"):
291 new_fmri
= expand_fmri(fmri_str
)
292 if new_fmri
and new_fmri
not in s
:
293 _get_dependencies(s
, new_fmri
, xport_cfg
, tracker
)
297 """Takes a manifest and return
298 (get_bytes, get_files, send_bytes, send_comp_bytes) tuple."""
305 for a
in mfst
.gen_actions():
307 getb
+= get_pkg_otw_size(a
)
309 sendb
+= int(a
.attrs
.get("pkg.size", 0))
310 sendcb
+= int(a
.attrs
.get("pkg.csize", 0))
311 if a
.name
== "signature":
312 getf
+= len(a
.get_chain_certs())
313 getb
+= a
.get_action_chain_csize()
314 return getb
, getf
, sendb
, sendcb
316 def add_hashes_to_multi(mfst
, multi
):
317 """Takes a manifest and a multi object and adds the hashes to the multi
320 for a
in mfst
.gen_actions():
324 def prune(fmri_list
, all_versions
, all_timestamps
):
325 """Returns a filtered version of fmri_list based on the provided
333 dedup
.setdefault(f
.get_short_fmri(), []).append(f
)
334 fmri_list
= [sorted(dedup
[f
], reverse
=True)[0] for f
in dedup
]
338 dedup
.setdefault(f
.pkg_name
, []).append(f
)
339 fmri_list
= [sorted(dedup
[f
], reverse
=True)[0] for f
in dedup
]
342 def fetch_catalog(src_pub
, tracker
, txport
, target_catalog
,
343 include_updates
=False):
344 """Fetch the catalog from src_uri.
346 target_catalog is a hint about whether this is a destination catalog,
347 which helps the progress tracker render the refresh output properly."""
349 src_uri
= src_pub
.repository
.origins
[0].uri
350 tracker
.refresh_start(1, full_refresh
=True,
351 target_catalog
=target_catalog
)
352 tracker
.refresh_start_pub(src_pub
)
354 if not src_pub
.meta_root
:
355 # Create a temporary directory for catalog.
356 cat_dir
= tempfile
.mkdtemp(dir=temp_root
,
357 prefix
=global_settings
.client_name
+ "-")
358 tmpdirs
.append(cat_dir
)
359 src_pub
.meta_root
= cat_dir
361 src_pub
.transport
= txport
363 src_pub
.refresh(full_refresh
=True, immediate
=True,
364 progtrack
=tracker
, include_updates
=include_updates
)
365 except apx
.TransportError
as e
:
366 # Assume that a catalog doesn't exist for the target publisher,
367 # and drive on. If there was an actual failure due to a
368 # transport issue, let the failure happen whenever some other
369 # operation is attempted later.
370 return catalog
.Catalog(read_only
=True)
372 tracker
.refresh_end_pub(src_pub
)
373 tracker
.refresh_done()
375 return src_pub
.catalog
378 global archive
, cache_dir
, download_start
, xport
, xport_cfg
, \
379 dest_xport
, temp_root
, targ_pub
, target
381 all_timestamps
= True
384 keep_compressed
= False
399 temp_root
= misc
.config_temp_root()
401 gettext
.install("pkg", "/usr/share/locale",
402 codeset
=locale
.getpreferredencoding())
404 global_settings
.client_name
= "pkgrecv"
405 target
= os
.environ
.get("PKG_DEST", None)
406 src_uri
= os
.environ
.get("PKG_SRC", None)
409 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "ac:D:d:hkm:np:rs:v",
410 ["cert=", "key=", "dcert=", "dkey=", "newest", "raw",
412 except getopt
.GetoptError
as e
:
413 usage(_("Illegal option -- {0}").format(e
.opt
))
415 for opt
, arg
in opts
:
420 elif opt
== "--clone":
424 elif opt
== "-D" or opt
== "--debug":
425 if arg
in ["plan", "transport"]:
430 key
, value
= arg
.split("=", 1)
431 except (AttributeError, ValueError):
432 usage(_("{opt} takes argument of form "
433 "name=value, not {arg}").format(
435 DebugValues
.set_value(key
, value
)
439 keep_compressed
= True
441 if arg
== "all-timestamps":
442 all_timestamps
= True
444 elif arg
== "all-versions":
445 all_timestamps
= False
447 elif arg
== "latest":
448 all_timestamps
= False
451 usage(_("Illegal option value -- {0}").format(
456 publishers
.append(arg
)
463 elif opt
== "--newest":
469 elif opt
== "--cert":
471 elif opt
== "--dkey":
473 elif opt
== "--dcert":
476 if not list_newest
and not target
:
477 usage(_("a destination must be provided"))
480 usage(_("a source repository must be provided"))
482 src_uri
= misc
.parse_uri(src_uri
)
485 cache_dir
= tempfile
.mkdtemp(dir=temp_root
,
486 prefix
=global_settings
.client_name
+ "-")
487 # Only clean-up cache dir if implicitly created by pkgrecv.
488 # User's cache-dirs should be preserved
489 tmpdirs
.append(cache_dir
)
492 usage(_("--clone can not be used with -c.\n"
493 "Content will be downloaded directly to the "
494 "destination repository and re-downloading after a "
495 "pkgrecv failure will not be required."))
498 usage(_("--clone can not be used with --raw.\n"))
500 if clone
and archive
:
501 usage(_("--clone can not be used with -a.\n"))
503 if clone
and list_newest
:
504 usage(_("--clone can not be used with --newest.\n"))
507 usage(_("--clone does not support FMRI patterns"))
509 if publishers
and not clone
:
510 usage(_("-p can only be used with --clone.\n"))
512 incoming_dir
= tempfile
.mkdtemp(dir=temp_root
,
513 prefix
=global_settings
.client_name
+ "-")
514 tmpdirs
.append(incoming_dir
)
516 # Create transport and transport config
517 xport
, xport_cfg
= transport
.setup_transport()
518 xport_cfg
.add_cache(cache_dir
, readonly
=False)
519 xport_cfg
.incoming_root
= incoming_dir
521 # Since publication destinations may only have one repository configured
522 # per publisher, create destination as separate transport in case source
523 # and destination have identical publisher configuration but different
524 # repository endpoints.
525 dest_xport
, dest_xport_cfg
= transport
.setup_transport()
526 dest_xport_cfg
.add_cache(cache_dir
, readonly
=False)
527 dest_xport_cfg
.incoming_root
= incoming_dir
529 # Configure src publisher(s).
530 transport
.setup_publisher(src_uri
, "source", xport
, xport_cfg
,
531 remote_prefix
=True, ssl_key
=key
, ssl_cert
=cert
)
533 args
= (pargs
, target
, list_newest
, all_versions
,
534 all_timestamps
, keep_compressed
, raw
, recursive
, dry_run
, verbose
,
535 dest_xport_cfg
, src_uri
, dkey
, dcert
)
538 args
+= (publishers
,)
539 return clone_repo(*args
)
542 # Retrieving package data for archival requires a different mode
543 # of operation so gets its own routine. Notably, it requires
544 # that all package data be retrieved before the archival process
546 return archive_pkgs(*args
)
548 # Normal package transfer allows operations on a per-package basis.
549 return transfer_pkgs(*args
)
551 def check_processed(any_matched
, any_unmatched
, total_processed
):
552 # Reduce unmatched patterns to those that were unmatched for all
554 unmatched
= set(any_unmatched
) - set(any_matched
)
559 # If any match failures remain, abort with an error.
561 if total_processed
> 0:
563 abort(str(apx
.PackageMatchErrors(unmatched_fmris
=unmatched
)),
566 def get_matches(src_pub
, tracker
, xport
, pargs
, any_unmatched
, any_matched
,
567 all_versions
, all_timestamps
, recursive
):
568 """Returns the set of matching FMRIs for the given arguments."""
571 src_cat
= fetch_catalog(src_pub
, tracker
, xport
, False)
572 # Avoid overhead of going through matching if user requested all
574 if "*" not in pargs
and "*@*" not in pargs
:
576 matches
, refs
, unmatched
= \
577 src_cat
.get_matching_fmris(pargs
)
578 except apx
.PackageMatchErrors
as e
:
581 # Track anything that failed to match.
582 any_unmatched
.extend(unmatched
)
583 any_matched
.extend(set(p
for p
in refs
.values()))
584 matches
= list(set(f
for m
in matches
.values() for f
in m
))
586 matches
= [f
for f
in src_cat
.fmris()]
589 # No matches at all; nothing to do for this publisher.
592 matches
= prune(matches
, all_versions
, all_timestamps
)
594 msg(_("Retrieving manifests for dependency "
596 matches
= prune(get_dependencies(matches
, xport_cfg
, tracker
),
597 all_versions
, all_timestamps
)
601 def archive_pkgs(pargs
, target
, list_newest
, all_versions
, all_timestamps
,
602 keep_compresed
, raw
, recursive
, dry_run
, verbose
, dest_xport_cfg
, src_uri
,
604 """Retrieve source package data completely and then archive it."""
606 global cache_dir
, download_start
, xport
, xport_cfg
608 target
= os
.path
.abspath(target
)
609 if os
.path
.exists(target
):
610 error(_("Target archive '{0}' already "
611 "exists.").format(target
))
614 # Open the archive early so that permissions failures, etc. can be
615 # detected before actual work is started.
617 pkg_arc
= pkg
.p5p
.Archive(target
, mode
="w")
619 basedir
= tempfile
.mkdtemp(dir=temp_root
,
620 prefix
=global_settings
.client_name
+ "-")
621 tmpdirs
.append(basedir
)
623 # Retrieve package data for all publishers.
626 invalid_manifests
= []
630 for src_pub
in xport_cfg
.gen_publishers():
631 # Root must be per publisher on the off chance that multiple
632 # publishers have the same package.
633 xport_cfg
.pkg_root
= os
.path
.join(basedir
, src_pub
.prefix
)
635 tracker
= get_tracker()
636 msg(_("Retrieving packages for publisher {0} ...").format(
638 if pargs
== None or len(pargs
) == 0:
639 usage(_("must specify at least one pkgfmri"))
641 matches
= get_matches(src_pub
, tracker
, xport
, pargs
,
642 any_unmatched
, any_matched
, all_versions
, all_timestamps
,
645 # No matches at all; nothing to do for this publisher.
648 # First, retrieve the manifests and calculate package transfer
655 msg(_("Retrieving and evaluating {0:d} package(s)...").format(
659 tracker
.manifest_fetch_start(npkgs
)
664 m
= get_manifest(f
, xport_cfg
)
665 except apx
.InvalidPackageErrors
as e
:
666 invalid_manifests
.extend(e
.errors
)
668 good_matches
.append(f
)
669 getb
, getf
, arcb
, arccb
= get_sizes(m
)
673 # Since files are going into the archive, progress
674 # can be tracked in terms of compressed bytes for
675 # the package files themselves.
678 # Also include the the manifest file itself in the
679 # amount of bytes to archive.
681 fs
= os
.stat(m
.pathname
)
682 arc_bytes
+= fs
.st_size
683 except EnvironmentError as e
:
684 raise apx
._convert
_error
(e
)
686 tracker
.manifest_fetch_progress(completion
=True)
687 matches
= good_matches
689 tracker
.manifest_fetch_done()
691 # Next, retrieve the content for this publisher's packages.
692 tracker
.download_set_goal(len(matches
), get_files
,
697 msg(_("\nArchiving packages ..."))
699 msg(_("\nArchiving packages (dry-run) ..."))
701 status
.append((_("Packages to add:"), str(len(matches
))))
702 status
.append((_("Files to retrieve:"), str(get_files
)))
703 status
.append((_("Estimated transfer size:"),
704 misc
.bytes_to_str(get_bytes
)))
706 rjust_status
= max(len(s
[0]) for s
in status
)
707 rjust_value
= max(len(s
[1]) for s
in status
)
709 msg("{0} {1}".format(s
[0].rjust(rjust_status
),
710 s
[1].rjust(rjust_value
)))
712 msg(_("\nPackages to archive:"))
713 for f
in sorted(matches
):
714 fmri
= f
.get_fmri(anarchy
=True,
715 include_scheme
=False)
720 # Don't call download_done here; it would cause an
721 # assertion failure since nothing was downloaded.
722 # Instead, call the method that simply finishes
723 # up the progress output.
724 tracker
.download_done(dryrun
=True)
726 total_processed
= len(matches
)
730 tracker
.download_start_pkg(f
)
731 pkgdir
= xport_cfg
.get_pkg_dir(f
)
732 mfile
= xport
.multi_file_ni(src_pub
, pkgdir
,
734 m
= get_manifest(f
, xport_cfg
)
735 add_hashes_to_multi(m
, mfile
)
738 download_start
= True
742 archive_list
.append((f
, m
.pathname
, pkgdir
))
744 # Nothing more to do for this package.
745 tracker
.download_end_pkg(f
)
748 tracker
.download_done()
751 # Check processed patterns and abort with failure if some were
753 check_processed(any_matched
, any_unmatched
, total_processed
)
756 # Now create archive and then archive retrieved package data.
758 pfmri
, mpath
, pkgdir
= archive_list
.pop()
759 pkg_arc
.add_package(pfmri
, mpath
, pkgdir
)
760 pkg_arc
.close(progtrack
=tracker
)
762 # Dump all temporary data.
765 if invalid_manifests
:
766 error(_("The following errors were encountered. The packages "
767 "listed were not\nreceived.\n{0}").format(
768 "\n".join(str(im
) for im
in invalid_manifests
)))
769 if invalid_manifests
and total_processed
:
770 return pkgdefs
.EXIT_PARTIAL
771 if invalid_manifests
:
772 return pkgdefs
.EXIT_OOPS
773 return pkgdefs
.EXIT_OK
776 def clone_repo(pargs
, target
, list_newest
, all_versions
, all_timestamps
,
777 keep_compressed
, raw
, recursive
, dry_run
, verbose
, dest_xport_cfg
, src_uri
,
778 dkey
, dcert
, publishers
):
780 global cache_dir
, download_start
, xport
, xport_cfg
, dest_xport
782 invalid_manifests
= []
784 modified_pubs
= set()
787 del_search_index
= set()
789 # Turn target into a valid URI.
790 target
= publisher
.RepositoryURI(misc
.parse_uri(target
))
792 if target
.scheme
!= "file":
793 abort(err
=_("Destination clone repository must be "
794 "filesystem-based."))
796 # Initialize the target repo.
798 repo
= sr
.Repository(read_only
=False,
799 root
=target
.get_pathname())
800 except sr
.RepositoryInvalidError
as e
:
801 txt
= str(e
) + "\n\n"
802 txt
+= _("To create a repository, use the pkgrepo command.")
805 def copy_catalog(src_cat_root
, pub
):
806 # Copy catalog files.
807 c_root
= repo
.get_pub_rstore(pub
).catalog_root
808 rstore_root
= repo
.get_pub_rstore(pub
).root
810 # We just use mkdtemp() to find ourselves a directory
811 # which does not already exist. The created dir is not
813 old_c_root
= tempfile
.mkdtemp(dir=rstore_root
,
815 shutil
.rmtree(old_c_root
)
816 shutil
.move(c_root
, old_c_root
)
817 shutil
.copytree(src_cat_root
, c_root
)
818 except Exception as e
:
819 abort(err
=_("Unable to copy catalog files: {0}").format(
823 # Check if all publishers in src are also in target. If not, add
824 # depending on what publishers were specified by user.
828 for sp
in xport_cfg
.gen_publishers():
829 src_pubs
[sp
.prefix
] = sp
830 dst_pubs
= repo
.get_publishers()
832 pubs_specified
= False
835 if p
not in src_pubs
and p
!= '*':
836 abort(err
=_("The publisher {0} does not exist in the "
837 "source repository.".format(p
)))
838 pubs_specified
= True
841 if sp
not in dst_pubs
and (sp
in publishers
or \
843 pubs_to_add
.append(src_pubs
[sp
])
844 pubs_to_sync
.append(src_pubs
[sp
])
845 elif sp
in dst_pubs
and (sp
in publishers
or '*' in publishers
846 or not pubs_specified
):
847 pubs_to_sync
.append(src_pubs
[sp
])
848 elif not pubs_specified
:
849 unknown_pubs
.append(sp
)
851 # We only print warning if the user didn't specify any valid publishers
853 if len(unknown_pubs
):
854 txt
= _("\nThe following publishers are present in the "
855 "source repository but not in the target repository.\n"
856 "Please use -p to specify which publishers need to be "
857 "cloned or -p '*' to clone all publishers.")
858 for p
in unknown_pubs
:
859 txt
+= "\n {0}\n".format(p
)
862 # Create non-existent publishers.
863 for p
in pubs_to_add
:
865 msg(_("Adding publisher {0} ...").format(p
.prefix
))
866 # add_publisher() will create a p5i file in the repo
867 # store, containing origin and possible mirrors from
868 # the src repo. These may not be valid for the new repo
869 # so skip creation of this file.
870 repo
.add_publisher(p
, skip_config
=True)
872 msg(_("Adding publisher {0} (dry-run) ...").format(
875 for src_pub
in pubs_to_sync
:
876 msg(_("Processing packages for publisher {0} ...").format(
878 tracker
= get_tracker()
880 src_basedir
= tempfile
.mkdtemp(dir=temp_root
,
881 prefix
=global_settings
.client_name
+ "-")
882 tmpdirs
.append(src_basedir
)
884 xport_cfg
.pkg_root
= src_basedir
886 # We make the destination repo our cache directory to save on
887 # IOPs. Have to remove all the old caches first.
889 xport_cfg
.clear_caches(shared
=True)
891 repo
.get_pub_rstore(src_pub
.prefix
).file_root
,
894 # Retrieve src and dest catalog for comparison.
895 src_pub
.meta_root
= src_basedir
897 src_cat
= fetch_catalog(src_pub
, tracker
, xport
, False,
898 include_updates
=True)
899 src_cat_root
= src_cat
.meta_root
902 targ_cat
= repo
.get_catalog(pub
=src_pub
.prefix
)
903 except sr
.RepositoryUnknownPublisher
:
904 targ_cat
= catalog
.Catalog(read_only
=True)
906 src_fmris
= set([x
for x
in src_cat
.fmris(last
=False)])
907 targ_fmris
= set([x
for x
in targ_cat
.fmris(last
=False)])
915 # We use bulk prefetching for faster transport of the manifests.
916 # Prefetch requires an intent which it sends to the server. Here
917 # we just use operation=clone for all FMRIs.
918 intent
= "operation=clone;"
920 # Find FMRIs which need to be added/removed.
921 to_add_set
= src_fmris
- targ_fmris
922 to_rm
= targ_fmris
- src_fmris
925 to_add
.append((f
, intent
))
931 # We have to do package removal first because after the sync we
932 # don't have the old catalog anymore and if we delete packages
933 # after the sync based on the current catalog we might delete
934 # files required by packages still in the repo.
936 msg(_("Packages to remove:"))
938 msg(" {0}".format(f
.get_fmri(anarchy
=True,
939 include_build
=False)))
942 msg(_("Removing packages ..."))
943 if repo
.get_pub_rstore(
944 src_pub
.prefix
).search_available
:
945 del_search_index
.add(src_pub
.prefix
)
946 repo
.remove_packages(to_rm
, progtrack
=tracker
,
949 total_processed
+= len(to_rm
)
950 modified_pubs
.add(src_pub
.prefix
)
953 msg(_("No packages to add."))
955 old_c_root
[src_pub
.prefix
] = copy_catalog(
956 src_cat_root
, src_pub
.prefix
)
962 msg(_("Retrieving and evaluating {0:d} package(s)...").format(
965 # Retrieve manifests.
966 # Try prefetching manifests in bulk first for faster, parallel
967 # transport. Retryable errors during prefetch are ignored and
968 # manifests are retrieved again during the "Reading" phase.
969 src_pub
.transport
.prefetch_manifests(to_add
, progtrack
=tracker
)
971 # Need to change the output of mfst_fetch since otherwise we
972 # would see "Download Manifests x/y" twice, once from the
973 # prefetch and once from the actual manifest analysis.
974 old_gti
= tracker
.mfst_fetch
975 tracker
.mfst_fetch
= progress
.GoalTrackerItem(
976 _("Reading Manifests"))
977 tracker
.manifest_fetch_start(len(to_add
))
980 m
= get_manifest(f
, xport_cfg
)
981 except apx
.InvalidPackageErrors
as e
:
982 invalid_manifests
.extend(e
.errors
)
984 getb
, getf
, sendb
, sendcb
= get_sizes(m
)
989 tracker
.manifest_fetch_progress(completion
=True)
992 # Move manifest into dest repo.
993 targ_path
= os
.path
.join(
994 repo
.get_pub_rstore(src_pub
.prefix
).root
, 'pkg')
995 dp
= m
.fmri
.get_dir_path()
996 dst_path
= os
.path
.join(targ_path
, dp
)
997 src_path
= os
.path
.join(src_basedir
, dp
, 'manifest')
998 dir_name
= os
.path
.dirname(dst_path
)
1000 misc
.makedirs(dir_name
)
1001 shutil
.move(src_path
, dst_path
)
1002 except Exception as e
:
1003 txt
= _("Unable to copy manifest: {0}").format(e
)
1006 tracker
.manifest_fetch_progress(completion
=True)
1008 tracker
.manifest_fetch_done()
1009 # Restore old GoalTrackerItem for manifest download.
1010 tracker
.mfst_fetch
= old_gti
1014 msg(_("\nRetrieving packages ..."))
1016 msg(_("\nRetrieving packages (dry-run) ..."))
1019 status
.append((_("Packages to add:"), str(len(to_add
))))
1020 status
.append((_("Files to retrieve:"), str(get_files
)))
1021 status
.append((_("Estimated transfer size:"),
1022 misc
.bytes_to_str(get_bytes
)))
1024 rjust_status
= max(len(s
[0]) for s
in status
)
1025 rjust_value
= max(len(s
[1]) for s
in status
)
1027 msg("{0} {1}".format(s
[0].rjust(rjust_status
),
1028 s
[1].rjust(rjust_value
)))
1030 msg(_("\nPackages to transfer:"))
1031 for f
, i
in sorted(to_add
):
1032 fmri
= f
.get_fmri(anarchy
=True,
1033 include_scheme
=False)
1034 msg("{0}".format(fmri
))
1040 tracker
.download_set_goal(len(to_add
), get_files
, get_bytes
)
1042 # Retrieve package files.
1044 tracker
.download_start_pkg(f
)
1045 mfile
= xport
.multi_file_ni(src_pub
, None,
1047 m
= get_manifest(f
, xport_cfg
)
1048 add_hashes_to_multi(m
, mfile
)
1053 tracker
.download_end_pkg(f
)
1054 total_processed
+= 1
1056 tracker
.download_done
1059 modified_pubs
.add(src_pub
.prefix
)
1060 old_c_root
[src_pub
.prefix
] = copy_catalog(src_cat_root
,
1063 if invalid_manifests
:
1064 error(_("The following packages could not be retrieved:\n{0}").format(
1065 "\n".join(str(im
) for im
in invalid_manifests
)))
1068 # Run pkgrepo verify to check repo.
1070 msg(_("\n\nVerifying repository contents."))
1071 cmd
= os
.path
.join(os
.path
.dirname(misc
.api_cmdpath()),
1073 args
= [cmd
, 'verify', '-s',
1074 target
.get_pathname(), '--disable', 'dependency']
1077 ret
= subprocess
.call(args
)
1078 except OSError as e
:
1079 raise RuntimeError("cannot execute {0}: {1}".format(
1082 # Cleanup. If verification was ok, remove backup copy of old catalog.
1083 # If not, move old catalog back into place and remove messed up catalog.
1084 for pub
in modified_pubs
:
1085 c_root
= repo
.get_pub_rstore(pub
).catalog_root
1088 shutil
.rmtree(c_root
)
1089 shutil
.move(old_c_root
[pub
], c_root
)
1091 shutil
.rmtree(old_c_root
[pub
])
1092 except Exception as e
:
1093 error(_("Unable to remove catalog files: {0}").format(e
))
1094 # We don't abort here to make sure we can
1095 # restore/delete as much as we can.
1099 txt
= _("Pkgrepo verify found errors in the updated repository."
1100 "\nThe original package catalog has been restored.\n")
1102 txt
+= _("Deleted packages can not be restored.\n")
1103 txt
+= _("The clone operation can be retried; package content "
1104 "that has already been retrieved will not be downloaded "
1108 if del_search_index
:
1109 txt
= _("\nThe search index for the following publishers has "
1110 "been removed due to package removals.\n")
1111 for p
in del_search_index
:
1112 txt
+= " {0}\n".format(p
)
1113 txt
+= _("\nTo restore the search index for all publishers run"
1114 "\n'pkgrepo refresh --no-catalog -s {0}'.\n").format(
1115 target
.get_pathname())
1119 if invalid_manifests
and total_processed
:
1120 return pkgdefs
.EXIT_PARTIAL
1121 if invalid_manifests
:
1122 return pkgdefs
.EXIT_OOPS
1123 return pkgdefs
.EXIT_OK
1125 def transfer_pkgs(pargs
, target
, list_newest
, all_versions
, all_timestamps
,
1126 keep_compressed
, raw
, recursive
, dry_run
, verbose
, dest_xport_cfg
, src_uri
,
1128 """Retrieve source package data and optionally republish it as each
1129 package is retrieved.
1132 global cache_dir
, download_start
, xport
, xport_cfg
, dest_xport
, targ_pub
1136 invalid_manifests
= []
1139 for src_pub
in xport_cfg
.gen_publishers():
1140 tracker
= get_tracker()
1142 # Make sure the prog tracker knows we're doing a listing
1143 # operation so that it suppresses irrelevant output.
1144 tracker
.set_purpose(tracker
.PURPOSE_LISTING
)
1146 if pargs
or len(pargs
) > 0:
1147 usage(_("--newest takes no options"))
1149 src_cat
= fetch_catalog(src_pub
, tracker
,
1151 for f
in src_cat
.fmris(ordered
=True, last
=True):
1152 msg(f
.get_fmri(include_build
=False))
1155 msg(_("Processing packages for publisher {0} ...").format(
1157 if pargs
== None or len(pargs
) == 0:
1158 usage(_("must specify at least one pkgfmri"))
1163 basedir
= tempfile
.mkdtemp(dir=temp_root
,
1164 prefix
=global_settings
.client_name
+ "-")
1165 tmpdirs
.append(basedir
)
1168 # Turn target into a valid URI.
1169 target
= misc
.parse_uri(target
)
1171 # Setup target for transport.
1172 targ_pub
= transport
.setup_publisher(target
,
1173 src_pub
.prefix
, dest_xport
, dest_xport_cfg
,
1174 ssl_key
=dkey
, ssl_cert
=dcert
)
1176 # Files have to be decompressed for republishing.
1177 keep_compressed
= False
1178 if target
.startswith("file://"):
1179 # Check to see if the repository exists first.
1181 t
= trans
.Transaction(target
,
1182 xport
=dest_xport
, pub
=targ_pub
)
1183 except trans
.TransactionRepositoryInvalidError
as e
:
1184 txt
= str(e
) + "\n\n"
1185 txt
+= _("To create a repository, use "
1186 "the pkgrepo command.")
1188 except trans
.TransactionRepositoryConfigError
as e
:
1189 txt
= str(e
) + "\n\n"
1190 txt
+= _("The repository configuration "
1191 "for the repository located at "
1192 "'{0}' is not valid or the "
1193 "specified path does not exist. "
1194 "Please correct the configuration "
1195 "of the repository or create a new "
1196 "one.").format(target
)
1198 except trans
.TransactionError
as e
:
1201 basedir
= target
= os
.path
.abspath(target
)
1202 if not os
.path
.exists(basedir
):
1204 os
.makedirs(basedir
, misc
.PKG_DIR_MODE
)
1205 except Exception as e
:
1206 error(_("Unable to create basedir "
1207 "'{dir}': {err}").format(
1208 dir=basedir
, err
=e
))
1211 xport_cfg
.pkg_root
= basedir
1212 dest_xport_cfg
.pkg_root
= basedir
1215 targ_cat
= fetch_catalog(targ_pub
, tracker
,
1218 matches
= get_matches(src_pub
, tracker
, xport
, pargs
,
1219 any_unmatched
, any_matched
, all_versions
, all_timestamps
,
1222 # No matches at all; nothing to do for this publisher.
1225 def get_basename(pfmri
):
1226 open_time
= pfmri
.get_timestamp()
1227 return "{0:d}_{1}".format(
1228 calendar
.timegm(open_time
.utctimetuple()),
1229 urllib
.quote(str(pfmri
), ""))
1231 # First, retrieve the manifests and calculate package transfer
1233 npkgs
= len(matches
)
1239 msg(_("Retrieving and evaluating {0:d} package(s)...").format(
1242 tracker
.manifest_fetch_start(npkgs
)
1247 if republish
and targ_cat
.get_entry(f
):
1248 tracker
.manifest_fetch_progress(completion
=True)
1251 m
= get_manifest(f
, xport_cfg
)
1252 except apx
.InvalidPackageErrors
as e
:
1253 invalid_manifests
.extend(e
.errors
)
1255 pkgs_to_get
.append(f
)
1257 getb
, getf
, sendb
, sendcb
= get_sizes(m
)
1261 # For now, normal republication always uses
1262 # uncompressed data as already compressed data
1263 # is not supported for publication.
1266 tracker
.manifest_fetch_progress(completion
=True)
1267 tracker
.manifest_fetch_done()
1269 # Next, retrieve and store the content for each package.
1270 tracker
.republish_set_goal(len(pkgs_to_get
), get_bytes
,
1275 msg(_("\nRetrieving packages ..."))
1277 msg(_("\nRetrieving packages (dry-run) ..."))
1279 status
.append((_("Packages to add:"),
1280 str(len(pkgs_to_get
))))
1281 status
.append((_("Files to retrieve:"),
1283 status
.append((_("Estimated transfer size:"),
1284 misc
.bytes_to_str(get_bytes
)))
1286 rjust_status
= max(len(s
[0]) for s
in status
)
1287 rjust_value
= max(len(s
[1]) for s
in status
)
1289 msg("{0} {1}".format(s
[0].rjust(rjust_status
),
1290 s
[1].rjust(rjust_value
)))
1292 msg(_("\nPackages to transfer:"))
1293 for f
in sorted(pkgs_to_get
):
1294 fmri
= f
.get_fmri(anarchy
=True,
1295 include_scheme
=False)
1296 msg("{0}".format(fmri
))
1300 tracker
.republish_done(dryrun
=True)
1305 pkgs_to_get
= sorted(pkgs_to_get
)
1306 for f
in pkgs_to_get
:
1307 tracker
.republish_start_pkg(f
)
1308 pkgdir
= xport_cfg
.get_pkg_dir(f
)
1309 mfile
= xport
.multi_file_ni(src_pub
, pkgdir
,
1310 not keep_compressed
, tracker
)
1311 m
= get_manifest(f
, xport_cfg
)
1312 add_hashes_to_multi(m
, mfile
)
1315 download_start
= True
1319 # Nothing more to do for this package.
1320 tracker
.republish_end_pkg(f
)
1323 # Get first line of original manifest so that inclusion
1324 # of the scheme can be determined.
1326 contents
= get_manifest(f
, xport_cfg
, contents
=True)
1327 if contents
.splitlines()[0].find("pkg:/") == -1:
1330 pkg_name
= f
.get_fmri(include_scheme
=use_scheme
)
1331 pkgdir
= xport_cfg
.get_pkg_dir(f
)
1333 # This is needed so any previous failures for a package
1335 trans_id
= get_basename(f
)
1338 targ_pub
= transport
.setup_publisher(target
,
1339 src_pub
.prefix
, dest_xport
, dest_xport_cfg
,
1340 remote_prefix
=True, ssl_key
=dkey
,
1344 t
= trans
.Transaction(target
, pkg_name
=pkg_name
,
1345 trans_id
=trans_id
, xport
=dest_xport
,
1346 pub
=targ_pub
, progtrack
=tracker
)
1348 # Remove any previous failed attempt to
1349 # to republish this package.
1351 t
.close(abandon
=True)
1353 # It might not exist already.
1357 for a
in m
.gen_actions():
1358 if a
.name
== "set" and \
1359 a
.attrs
.get("name", "") in ("fmri",
1361 # To be consistent with the
1362 # server, the fmri can't be
1363 # added to the manifest.
1366 if hasattr(a
, "hash"):
1367 fname
= os
.path
.join(pkgdir
,
1369 a
.data
= lambda: open(fname
,
1372 if a
.name
== "signature":
1373 # We always store content in the
1374 # repository by the least-
1376 for fp
in a
.get_chain_certs(
1377 least_preferred
=True):
1378 fname
= os
.path
.join(
1381 # Always defer catalog update.
1382 t
.close(add_to_catalog
=False)
1383 except trans
.TransactionError
as e
:
1386 # Dump data retrieved so far after each successful
1387 # republish to conserve space.
1389 shutil
.rmtree(dest_xport_cfg
.incoming_root
)
1390 shutil
.rmtree(pkgdir
)
1391 if cache_dir
in tmpdirs
:
1392 # If cache_dir is listed in tmpdirs,
1393 # then it's safe to dump cache contents.
1394 # Otherwise, it's a user cache directory
1395 # and shouldn't be dumped.
1396 shutil
.rmtree(cache_dir
)
1397 misc
.makedirs(cache_dir
)
1398 except EnvironmentError as e
:
1399 raise apx
._convert
_error
(e
)
1400 misc
.makedirs(dest_xport_cfg
.incoming_root
)
1403 tracker
.republish_end_pkg(f
)
1405 tracker
.republish_done()
1409 # If any packages were published, trigger an update of
1411 total_processed
+= processed
1412 dest_xport
.publish_refresh_packages(targ_pub
)
1414 # Prevent further use.
1417 # Check processed patterns and abort with failure if some were
1419 check_processed(any_matched
, any_unmatched
, total_processed
)
1421 # Dump all temporary data.
1423 if invalid_manifests
:
1424 error(_("The following errors were encountered. The packages "
1425 "listed were not\nreceived.\n{0}").format(
1426 "\n".join(str(im
) for im
in invalid_manifests
)))
1427 if invalid_manifests
and total_processed
:
1428 return pkgdefs
.EXIT_PARTIAL
1429 if invalid_manifests
:
1430 return pkgdefs
.EXIT_OOPS
1431 return pkgdefs
.EXIT_OK
1433 if __name__
== "__main__":
1435 # Make all warnings be errors.
1436 warnings
.simplefilter('error')
1440 except (KeyboardInterrupt, apx
.CanceledException
):
1447 except (pkg
.actions
.ActionError
, trans
.TransactionError
, RuntimeError,
1448 apx
.ApiException
) as _e
:
1457 # We don't want to display any messages here to prevent
1458 # possible further broken pipe (EPIPE) errors.
1465 except SystemExit as _e
:
1471 except EnvironmentError as _e
:
1472 if _e
.errno
!= errno
.ENOSPC
and _e
.errno
!= errno
.EDQUOT
:
1476 if _e
.errno
== errno
.EDQUOT
:
1477 txt
+= _("Storage space quota exceeded.")
1479 txt
+= _("No storage space left.")
1481 temp_root_path
= misc
.get_temp_root_path()
1482 tdirs
= [temp_root_path
]
1483 if cache_dir
not in tmpdirs
:
1484 # Only include in message if user specified.
1485 tdirs
.append(cache_dir
)
1486 if target
and target
.startswith("file://"):
1487 tdirs
.append(target
)
1490 error(txt
+ _("Please verify that the filesystem containing "
1491 "the following directories has enough space available:\n"
1492 "{0}").format("\n".join(tdirs
)))
1500 traceback
.print_exc()
1501 error(misc
.get_traceback_message())
1503 # Cleanup must be called *after* error messaging so that
1504 # exceptions processed during cleanup don't cause the wrong
1505 # traceback to be printed.