← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/copyPackage-no-send-email into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/copyPackage-no-send-email into lp:launchpad.

Requested reviews:
  William Grant (wgrant): code

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/copyPackage-no-send-email/+merge/231141
-- 
https://code.launchpad.net/~wgrant/launchpad/copyPackage-no-send-email/+merge/231141
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2014-08-06 16:10:18 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2014-08-18 02:18:13 +0000
@@ -1371,6 +1371,12 @@
             description=_("Automatically approve this copy (queue admins "
                           "only)."),
             required=False),
+        silent=Bool(
+            title=_("Silent"),
+            description=_(
+                "Don't notify anyone about this copy. For use by queue "
+                "admins only."),
+            required=False),
         from_pocket=TextLine(title=_("Source pocket name"), required=False),
         from_series=TextLine(
             title=_("Source distroseries name"), required=False),
@@ -1386,7 +1392,7 @@
     def copyPackage(source_name, version, from_archive, to_pocket,
                     person, to_series=None, include_binaries=False,
                     sponsored=None, unembargo=False, auto_approve=False,
-                    from_pocket=None, from_series=None,
+                    silent=False, from_pocket=None, from_series=None,
                     phased_update_percentage=None):
         """Copy a single named source into this archive.
 
@@ -1420,6 +1426,8 @@
         :param auto_approve: if True and the `IPerson` requesting the sync
             has queue admin permissions on the target, accept the copy
             immediately rather than setting it to unapproved.
+        :param silent: Suppress any emails that the copy would generate.
+            Only usable with queue admin permissions on the target.
         :param from_pocket: the source pocket (as a string). If omitted,
             copy from any pocket with a matching version.
         :param from_series: the source distroseries (as a string). If
@@ -1464,12 +1472,19 @@
             description=_("Automatically approve this copy (queue admins "
                           "only)."),
             required=False),
+        silent=Bool(
+            title=_("Silent"),
+            description=_(
+                "Don't notify anyone about this copy. For use by queue "
+                "admins only."),
+            required=False),
         )
     @export_write_operation()
     @operation_for_version('devel')
     def copyPackages(source_names, from_archive, to_pocket, person,
                      to_series=None, from_series=None, include_binaries=False,
-                     sponsored=None, unembargo=False, auto_approve=False):
+                     sponsored=None, unembargo=False, auto_approve=False,
+                     silent=False):
         """Copy multiple named sources into this archive from another.
 
         Asynchronously copy the most recent PUBLISHED versions of the named
@@ -1505,6 +1520,8 @@
         :param auto_approve: if True and the `IPerson` requesting the sync
             has queue admin permissions on the target, accept the copies
             immediately rather than setting it to unapproved.
+        :param silent: Suppress any emails that the copy would generate.
+            Only usable with queue admin permissions on the target.
 
         :raises NoSuchSourcePackageName: if the source name is invalid
         :raises PocketNotFound: if the pocket name is invalid

=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py	2013-06-21 09:36:02 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py	2014-08-18 02:18:13 +0000
@@ -129,7 +129,7 @@
                include_binaries=False, package_version=None,
                copy_policy=PackageCopyPolicy.INSECURE, requester=None,
                sponsored=None, unembargo=False, auto_approve=False,
-               source_distroseries=None, source_pocket=None,
+               silent=False, source_distroseries=None, source_pocket=None,
                phased_update_percentage=None):
         """Create a new `IPlainPackageCopyJob`.
 
@@ -152,6 +152,8 @@
         :param auto_approve: if True and the user requesting the sync has
             queue admin permissions on the target, accept the copy
             immediately rather than setting it to unapproved.
+        :param silent: Suppress any emails that the copy would generate.
+            Only usable with queue admin permissions on the target.
         :param source_distroseries: The `IDistroSeries` from which to copy
             the packages. If omitted, copy from any series with a matching
             version.
@@ -165,7 +167,7 @@
     def createMultiple(target_distroseries, copy_tasks, requester,
                        copy_policy=PackageCopyPolicy.INSECURE,
                        include_binaries=False, unembargo=False,
-                       auto_approve=False):
+                       auto_approve=False, silent=False):
         """Create multiple new `IPlainPackageCopyJob`s at once.
 
         :param target_distroseries: The `IDistroSeries` to which to copy the
@@ -177,10 +179,12 @@
         :param copy_policy: Applicable `PackageCopyPolicy`.
         :param include_binaries: As in `do_copy`.
         :param unembargo: As in `do_copy`.
+        :param auto_approve: if True and the user requesting the sync has
+            queue admin permissions on the target, accept the copy
+            immediately rather than setting it to unapproved.
+        :param silent: Suppress any emails that the copy would generate.
+            Only usable with queue admin permissions on the target.
         :return: An iterable of `PackageCopyJob` ids.
-        :param auto_approve: if True and the user requesting the sync has
-            queue admin permissions on the target, accept the copy
-            immediately rather than setting it to unapproved.
         """
 
     def getActiveJobs(target_archive):
@@ -237,6 +241,8 @@
         title=_("Automatic approval"),
         required=False, readonly=True)
 
+    silent = Bool(title=_("Silent"), required=False, readonly=True)
+
     source_distroseries = Reference(
         schema=IDistroSeries, title=_('Source DistroSeries.'),
         required=False, readonly=True)

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2014-08-09 19:31:03 +0000
+++ lib/lp/soyuz/model/archive.py	2014-08-18 02:18:13 +0000
@@ -1718,7 +1718,7 @@
     def copyPackage(self, source_name, version, from_archive, to_pocket,
                     person, to_series=None, include_binaries=False,
                     sponsored=None, unembargo=False, auto_approve=False,
-                    from_pocket=None, from_series=None,
+                    silent=False, from_pocket=None, from_series=None,
                     phased_update_percentage=None):
         """See `IArchive`."""
         # Asynchronously copy a package using the job system.
@@ -1754,14 +1754,14 @@
             package_version=version, include_binaries=include_binaries,
             copy_policy=PackageCopyPolicy.INSECURE, requester=person,
             sponsored=sponsored, unembargo=unembargo,
-            auto_approve=auto_approve, source_distroseries=from_series,
-            source_pocket=from_pocket,
+            auto_approve=auto_approve, silent=silent,
+            source_distroseries=from_series, source_pocket=from_pocket,
             phased_update_percentage=phased_update_percentage)
 
     def copyPackages(self, source_names, from_archive, to_pocket,
                      person, to_series=None, from_series=None,
                      include_binaries=None, sponsored=None, unembargo=False,
-                     auto_approve=False):
+                     auto_approve=False, silent=False):
         """See `IArchive`."""
         from lp.soyuz.scripts.packagecopier import check_copy_permissions
         sources = self._collectLatestPublishedSources(
@@ -1789,7 +1789,7 @@
         job_source.createMultiple(
             copy_tasks, person, copy_policy=PackageCopyPolicy.MASS_SYNC,
             include_binaries=include_binaries, sponsored=sponsored,
-            unembargo=unembargo, auto_approve=auto_approve)
+            unembargo=unembargo, auto_approve=auto_approve, silent=silent)
 
     def _collectLatestPublishedSources(self, from_archive, from_series,
                                        source_names):

=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py	2014-07-23 13:25:14 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py	2014-08-18 02:18:13 +0000
@@ -275,8 +275,9 @@
     @classmethod
     def _makeMetadata(cls, target_pocket, package_version,
                       include_binaries, sponsored=None, unembargo=False,
-                      auto_approve=False, source_distroseries=None,
-                      source_pocket=None, phased_update_percentage=None):
+                      auto_approve=False, silent=False,
+                      source_distroseries=None, source_pocket=None,
+                      phased_update_percentage=None):
         """Produce a metadata dict for this job."""
         return {
             'target_pocket': target_pocket.value,
@@ -285,6 +286,7 @@
             'sponsored': sponsored.name if sponsored else None,
             'unembargo': unembargo,
             'auto_approve': auto_approve,
+            'silent': silent,
             'source_distroseries':
                 source_distroseries.name if source_distroseries else None,
             'source_pocket': source_pocket.value if source_pocket else None,
@@ -297,15 +299,15 @@
                include_binaries=False, package_version=None,
                copy_policy=PackageCopyPolicy.INSECURE, requester=None,
                sponsored=None, unembargo=False, auto_approve=False,
-               source_distroseries=None, source_pocket=None,
+               silent=False, source_distroseries=None, source_pocket=None,
                phased_update_percentage=None):
         """See `IPlainPackageCopyJobSource`."""
         assert package_version is not None, "No package version specified."
         assert requester is not None, "No requester specified."
         metadata = cls._makeMetadata(
             target_pocket, package_version, include_binaries, sponsored,
-            unembargo, auto_approve, source_distroseries, source_pocket,
-            phased_update_percentage)
+            unembargo, auto_approve, silent, source_distroseries,
+            source_pocket, phased_update_percentage)
         job = PackageCopyJob(
             job_type=cls.class_job_type,
             source_archive=source_archive,
@@ -323,7 +325,7 @@
     @classmethod
     def _composeJobInsertionTuple(cls, copy_policy, include_binaries, job_id,
                                   copy_task, sponsored, unembargo,
-                                  auto_approve):
+                                  auto_approve, silent):
         """Create an SQL fragment for inserting a job into the database.
 
         :return: A string representing an SQL tuple containing initializers
@@ -340,7 +342,7 @@
         ) = copy_task
         metadata = cls._makeMetadata(
             target_pocket, package_version, include_binaries, sponsored,
-            unembargo, auto_approve)
+            unembargo, auto_approve, silent)
         data = (
             cls.class_job_type, target_distroseries, copy_policy,
             source_archive, target_archive, package_name, job_id,
@@ -351,14 +353,14 @@
     def createMultiple(cls, copy_tasks, requester,
                        copy_policy=PackageCopyPolicy.INSECURE,
                        include_binaries=False, sponsored=None,
-                       unembargo=False, auto_approve=False):
+                       unembargo=False, auto_approve=False, silent=False):
         """See `IPlainPackageCopyJobSource`."""
         store = IMasterStore(Job)
         job_ids = Job.createMultiple(store, len(copy_tasks), requester)
         job_contents = [
             cls._composeJobInsertionTuple(
                 copy_policy, include_binaries, job_id, task, sponsored,
-                unembargo, auto_approve)
+                unembargo, auto_approve, silent)
             for job_id, task in zip(job_ids, copy_tasks)]
         return bulk.create(
                 (PackageCopyJob.job_type, PackageCopyJob.target_distroseries,
@@ -445,6 +447,10 @@
         return self.metadata.get('auto_approve', False)
 
     @property
+    def silent(self):
+        return self.metadata.get('silent', False)
+
+    @property
     def source_distroseries(self):
         name = self.metadata.get('source_distroseries')
         if name is None:
@@ -462,6 +468,12 @@
     def phased_update_percentage(self):
         return self.metadata.get('phased_update_percentage')
 
+    @property
+    def requester_can_admin_target(self):
+        return self.target_archive.canAdministerQueue(
+            self.requester, self.getSourceOverride().component,
+            self.target_pocket, self.target_distroseries)
+
     def _createPackageUpload(self, unapproved=False):
         pu = self.target_distroseries.createQueueEntry(
             pocket=self.target_pocket, archive=self.target_archive,
@@ -524,10 +536,7 @@
 
         # Put the (existing or default) override in the metadata.
         self.addSourceOverride(override)
-        if auto_approve:
-            auto_approve = self.target_archive.canAdministerQueue(
-                self.requester, self.getSourceOverride().component,
-                self.target_pocket, self.target_distroseries)
+        auto_approve = auto_approve and self.requester_can_admin_target
 
         if override.new:
             approve_new = auto_approve or copy_policy.autoApproveNew(
@@ -617,6 +626,10 @@
             # Wrap any forbidden-pocket error in CannotCopy.
             raise CannotCopy(unicode(reason))
 
+        if self.silent and not self.requester_can_admin_target:
+            raise CannotCopy(
+                "Silent copies need queue admin privileges on the target.")
+
         source_package = self.findSourcePublication()
 
         # If there's a PackageUpload associated with this job then this
@@ -637,7 +650,8 @@
             pocket=self.target_pocket, exact_match=True)
         override = self.getSourceOverride()
         copy_policy = self.getPolicyImplementation()
-        send_email = copy_policy.send_email(self.target_archive)
+        send_email = (
+            copy_policy.send_email(self.target_archive) and not self.silent)
         copied_publications = do_copy(
             sources=[source_package], archive=self.target_archive,
             series=self.target_distroseries, pocket=self.target_pocket,

=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py	2014-07-29 07:29:32 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py	2014-08-18 02:18:13 +0000
@@ -12,6 +12,7 @@
     MatchesRegex,
     MatchesStructure,
     )
+from testtools.testcase import ExpectedException
 import transaction
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
@@ -65,7 +66,10 @@
     TestCaseWithFactory,
     verifyObject,
     )
-from lp.testing.dbuser import switch_dbuser
+from lp.testing.dbuser import (
+    dbuser,
+    switch_dbuser,
+    )
 from lp.testing.fakemethod import FakeMethod
 from lp.testing.layers import (
     CeleryJobLayer,
@@ -84,7 +88,7 @@
         DistroSeriesDifferenceComment.distro_series_difference == dsd)
 
 
-def create_proper_job(factory):
+def create_proper_job(factory, silent=False):
     """Create a job that will complete successfully."""
     publisher = SoyuzTestPublisher()
     with admin_logged_in():
@@ -123,7 +127,7 @@
         target_distroseries=target_series,
         target_pocket=PackagePublishingPocket.RELEASE,
         package_version="2.8-1", include_binaries=False,
-        requester=requester)
+        requester=requester, silent=silent)
 
 
 class LocalTestHelper:
@@ -1233,6 +1237,25 @@
         self.assertEqual(
             "Nancy Requester <requester@xxxxxxxxxxx>", emails[1]['From'])
 
+    def test_silent(self):
+        # Copies into a non-PPA archive normally send emails. They can
+        # be suppressed by queue admin with the 'silent' flag.
+        job = create_proper_job(self.factory, silent=True)
+        self.assertEqual("libc", job.package_name)
+        self.assertEqual("2.8-1", job.package_version)
+
+        # An unprivileged requester can't copy silently.
+        with dbuser(self.dbuser):
+            with ExpectedException(
+                    CannotCopy, "Silent copies need queue admin.*"):
+                removeSecurityProxy(job).attemptCopy()
+
+        # But if we give them queue admin privileges it works fine.
+        job.target_archive.newQueueAdmin(job.requester, "main")
+        with dbuser(self.dbuser):
+            removeSecurityProxy(job).attemptCopy()
+        self.assertEqual(0, len(pop_notifications()))
+
     def test_copying_closes_bugs(self):
         # Copying a package into a primary archive should close any bugs
         # mentioned in its changelog for versions added since the most


References