Merge lp:~cjwatson/launchpad/copy-ddtp into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: no longer in the source branch.
Merged at revision: 15598
Proposed branch: lp:~cjwatson/launchpad/copy-ddtp
Merge into: lp:launchpad
Diff against target: 225 lines (+85/-19)
7 files modified
lib/lp/archivepublisher/ddtp_tarball.py (+12/-2)
lib/lp/archivepublisher/debian_installer.py (+5/-2)
lib/lp/archivepublisher/dist_upgrader.py (+5/-2)
lib/lp/archivepublisher/tests/test_ddtp_tarball.py (+32/-1)
lib/lp/archivepublisher/uefi.py (+5/-2)
lib/lp/soyuz/scripts/custom_uploads_copier.py (+2/-3)
lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py (+24/-7)
To merge this branch: bzr merge lp:~cjwatson/launchpad/copy-ddtp
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+111688@code.launchpad.net

Commit message

Teach CustomUploadsCopier to copy ddtp-tarball files.

Description of the change

== Summary ==

Bug 827941 reports that there's another custom upload type, namely ddtp-tarball, that needs to be accounted for in CustomUploadsCopier so that it gets copied to new distroseries. (Bug 672314 has an example of a concrete problem that I think has this as its root cause.)

== Proposed fix ==

At one point this would have been fiddly, but following https://code.launchpad.net/~cjwatson/launchpad/custom-upload-parsing/+merge/107656 it's pretty easy: implement DdtpTarballUpload.getSeriesKey and add an item to CustomUploadsCopier.copyable_types. Job done.

== LOC Rationale ==

+46. However, this is part of an arc as follows:

  https://code.launchpad.net/~cjwatson/launchpad/pcj-reupload/+merge/111124 (+180)
  https://code.launchpad.net/~cjwatson/launchpad/copy-custom-uploads/+merge/111653 (+66)
  this branch (+46)
  a probable future branch to fix P-a-s handling in direct copies (should be <100)
  removal of unembargo-package, delayed copies, ubuntu-security celebrity, etc. (at least -600)

Even fairly conservatively, this arc should end up well in the black.

== Tests ==

bin/test -vvct test_ddtp_tarball -t test_custom_uploads_copier

== Demo and Q/A ==

It would be best to wait until after https://code.launchpad.net/~cjwatson/launchpad/copy-custom-uploads/+merge/111653 has landed. Once that's done, the same QA procedure as in that MP should work, except that the package to upload is whatever Michael uses to upload translations and the paths are dists/precise-updates/*/i18n/.

I won't be able to land and QA this until the issue I spotted in https://bugs.launchpad.net/launchpad/+bug/827941/comments/3 is fixed, because the current way that DDTP tarballs are uploaded is really not amenable to the package copier, owing to not having an SPPH. However, I believe this code will be fine once the uploads are fixed to be less evil (and we might even be able to delete a bit of code from archiveuploader as a result) so I might as well get this in for review anyway.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

In ddtp_tarball.py ( line 23 of the diff) there is this line:

    _, component, _ = tarfile_path.split("_")

Since you are interested in underscores in the file name and not
the entire path, os.path.basename(tarfile_path).split("_") would be
better.

It would also be good to have some tests with underscores in the path
that show that the method handles them correctly.

Using the ValueError as an way of determining that the destructuring
assignment failed seems a bit indirect as far as input validation goes.

Why is getSeriesKey a classmethod when "cls" isn't used? Maybe a
staticmethod is more appropriate.

How about this for getSeriesKey?

@staticmethod
def getSeriesKey(tarfile_path):
    fn = os.path.basename(tarfile_path)
    if fn.count('_') != 2:
        # The file name does not match the pattern, so no series key
        # can be extracted.
        return None
    return fn.split('_')[1]

Revision history for this message
Colin Watson (cjwatson) wrote :

On Mon, Jun 25, 2012 at 06:25:23PM -0000, Benji York wrote:
> In ddtp_tarball.py ( line 23 of the diff) there is this line:
>
> _, component, _ = tarfile_path.split("_")
>
> Since you are interested in underscores in the file name and not
> the entire path, os.path.basename(tarfile_path).split("_") would be
> better.

Yes, indeed. Much of this is changing anyway as a result of feedback on
https://code.launchpad.net/~cjwatson/launchpad/custom-uefi/+merge/111626;
as a result I think I'm going to put this branch on temporary hold until
that one's ready to land, rather than doing much the same work in two
branches. (For instance recent work on that branch means that
getSeriesKey has to be a classmethod, although a helper method it calls
can probably become a staticmethod.)

> Using the ValueError as an way of determining that the destructuring
> assignment failed seems a bit indirect as far as input validation goes.
[...]
> @staticmethod
> def getSeriesKey(tarfile_path):
> fn = os.path.basename(tarfile_path)
> if fn.count('_') != 2:
> # The file name does not match the pattern, so no series key
> # can be extracted.
> return None
> return fn.split('_')[1]

I don't much like the look-before-you-leap pattern here, TBH; it seems
easy for it to get out of sync as code mutates over time, especially
since "fn.count('_') != 2" and "fn.split('_')[1]" are spelled just
differently enough to make it easy to forget to change one of them.

Revision history for this message
Colin Watson (cjwatson) wrote :

I think this is ready for review again now, since custom-uefi has landed so I was able to rephrase this a little in terms of that.

Revision history for this message
Benji York (benji) wrote :

I still think the look-before-you-leap approach is better here, but there's no reason to hold this branch up on that.

review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) wrote :

I'm putting together a compromise that's a bit more explicit about the input validation without duplicating checking and parsing logic in quite the same way.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archivepublisher/ddtp_tarball.py'
--- lib/lp/archivepublisher/ddtp_tarball.py 2012-06-28 16:32:27 +0000
+++ lib/lp/archivepublisher/ddtp_tarball.py 2012-07-10 11:27:22 +0000
@@ -49,8 +49,11 @@
4949
50 @staticmethod50 @staticmethod
51 def parsePath(tarfile_path):51 def parsePath(tarfile_path):
52 name, component, version = os.path.basename(tarfile_path).split("_")52 tarfile_base = os.path.basename(tarfile_path)
53 return name, component, version53 bits = tarfile_base.split("_")
54 if len(bits) != 3:
55 raise ValueError("%s is not NAME_COMPONENT_VERSION" % tarfile_base)
56 return tuple(bits)
5457
55 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):58 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
56 _, component, self.version = self.parsePath(tarfile_path)59 _, component, self.version = self.parsePath(tarfile_path)
@@ -58,6 +61,13 @@
58 self.targetdir = os.path.join(61 self.targetdir = os.path.join(
59 pubconf.archiveroot, 'dists', distroseries, component)62 pubconf.archiveroot, 'dists', distroseries, component)
6063
64 @classmethod
65 def getSeriesKey(cls, tarfile_path):
66 try:
67 return cls.parsePath(tarfile_path)[1]
68 except ValueError:
69 return None
70
61 def checkForConflicts(self):71 def checkForConflicts(self):
62 # We just overwrite older files, so no conflicts are possible.72 # We just overwrite older files, so no conflicts are possible.
63 pass73 pass
6474
=== modified file 'lib/lp/archivepublisher/debian_installer.py'
--- lib/lp/archivepublisher/debian_installer.py 2012-06-28 16:32:27 +0000
+++ lib/lp/archivepublisher/debian_installer.py 2012-07-10 11:27:22 +0000
@@ -42,8 +42,11 @@
4242
43 @staticmethod43 @staticmethod
44 def parsePath(tarfile_path):44 def parsePath(tarfile_path):
45 base, version, arch = os.path.basename(tarfile_path).split("_")45 tarfile_base = os.path.basename(tarfile_path)
46 return base, version, arch.split(".")[0]46 bits = tarfile_base.split("_")
47 if len(bits) != 3:
48 raise ValueError("%s is not BASE_VERSION_ARCH" % tarfile_base)
49 return bits[0], bits[1], bits[2].split(".")[0]
4750
48 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):51 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
49 _, self.version, self.arch = self.parsePath(tarfile_path)52 _, self.version, self.arch = self.parsePath(tarfile_path)
5053
=== modified file 'lib/lp/archivepublisher/dist_upgrader.py'
--- lib/lp/archivepublisher/dist_upgrader.py 2012-07-03 17:09:00 +0000
+++ lib/lp/archivepublisher/dist_upgrader.py 2012-07-10 11:27:22 +0000
@@ -59,8 +59,11 @@
5959
60 @staticmethod60 @staticmethod
61 def parsePath(tarfile_path):61 def parsePath(tarfile_path):
62 name, version, arch = os.path.basename(tarfile_path).split("_")62 tarfile_base = os.path.basename(tarfile_path)
63 return name, version, arch.split(".")[0]63 bits = tarfile_base.split("_")
64 if len(bits) != 3:
65 raise ValueError("%s is not NAME_VERSION_ARCH" % tarfile_base)
66 return bits[0], bits[1], bits[2].split(".")[0]
6467
65 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):68 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
66 _, self.version, self.arch = self.parsePath(tarfile_path)69 _, self.version, self.arch = self.parsePath(tarfile_path)
6770
=== modified file 'lib/lp/archivepublisher/tests/test_ddtp_tarball.py'
--- lib/lp/archivepublisher/tests/test_ddtp_tarball.py 2012-06-28 16:32:27 +0000
+++ lib/lp/archivepublisher/tests/test_ddtp_tarball.py 2012-07-10 11:27:22 +0000
@@ -9,7 +9,10 @@
99
10import os10import os
1111
12from lp.archivepublisher.ddtp_tarball import process_ddtp_tarball12from lp.archivepublisher.ddtp_tarball import (
13 DdtpTarballUpload,
14 process_ddtp_tarball,
15 )
13from lp.services.tarfile_helpers import LaunchpadWriteTarFile16from lp.services.tarfile_helpers import LaunchpadWriteTarFile
14from lp.testing import TestCase17from lp.testing import TestCase
1518
@@ -107,3 +110,31 @@
107 self.assertEqual("", ca_file.read())110 self.assertEqual("", ca_file.read())
108 self.assertEqual(1, os.stat(bn).st_nlink)111 self.assertEqual(1, os.stat(bn).st_nlink)
109 self.assertEqual(1, os.stat(ca).st_nlink)112 self.assertEqual(1, os.stat(ca).st_nlink)
113
114 def test_parsePath_handles_underscore_in_directory(self):
115 # parsePath is not misled by an underscore in the directory name.
116 self.assertEqual(
117 # XXX cjwatson 2012-07-03: .tar.gz is not stripped off the end
118 # of the version due to something of an ambiguity in the design;
119 # how should translations_main_1.0.1.tar.gz be parsed? In
120 # practice this doesn't matter because DdtpTarballUpload never
121 # uses the version for anything.
122 ("translations", "main", "1.tar.gz"),
123 DdtpTarballUpload.parsePath(
124 "/dir_with_underscores/translations_main_1.tar.gz"))
125
126 def test_getSeriesKey_extracts_component(self):
127 # getSeriesKey extracts the component from an upload's filename.
128 self.openArchive("20060728")
129 self.assertEqual("main", DdtpTarballUpload.getSeriesKey(self.path))
130
131 def test_getSeriesKey_returns_None_on_mismatch(self):
132 # getSeriesKey returns None if the filename does not match the
133 # expected pattern.
134 self.assertIsNone(DdtpTarballUpload.getSeriesKey("argh_1.0.jpg"))
135
136 def test_getSeriesKey_refuses_names_with_wrong_number_of_fields(self):
137 # getSeriesKey requires exactly three fields.
138 self.assertIsNone(DdtpTarballUpload.getSeriesKey("package_1.0.tar.gz"))
139 self.assertIsNone(DdtpTarballUpload.getSeriesKey(
140 "one_two_three_four_5.tar.gz"))
110141
=== modified file 'lib/lp/archivepublisher/uefi.py'
--- lib/lp/archivepublisher/uefi.py 2012-06-28 16:32:27 +0000
+++ lib/lp/archivepublisher/uefi.py 2012-07-10 11:27:22 +0000
@@ -69,8 +69,11 @@
6969
70 @staticmethod70 @staticmethod
71 def parsePath(tarfile_path):71 def parsePath(tarfile_path):
72 loader_type, version, arch = os.path.basename(tarfile_path).split("_")72 tarfile_base = os.path.basename(tarfile_path)
73 return loader_type, version, arch.split(".")[0]73 bits = tarfile_base.split("_")
74 if len(bits) != 3:
75 raise ValueError("%s is not TYPE_VERSION_ARCH" % tarfile_base)
76 return bits[0], bits[1], bits[2].split(".")[0]
7477
75 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):78 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
76 if pubconf.uefiroot is None:79 if pubconf.uefiroot is None:
7780
=== modified file 'lib/lp/soyuz/scripts/custom_uploads_copier.py'
--- lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-07-03 17:09:00 +0000
+++ lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-07-10 11:27:22 +0000
@@ -16,6 +16,7 @@
1616
17from zope.component import getUtility17from zope.component import getUtility
1818
19from lp.archivepublisher.ddtp_tarball import DdtpTarballUpload
19from lp.archivepublisher.debian_installer import DebianInstallerUpload20from lp.archivepublisher.debian_installer import DebianInstallerUpload
20from lp.archivepublisher.dist_upgrader import DistUpgraderUpload21from lp.archivepublisher.dist_upgrader import DistUpgraderUpload
21from lp.archivepublisher.uefi import UefiUpload22from lp.archivepublisher.uefi import UefiUpload
@@ -41,6 +42,7 @@
41 copyable_types = {42 copyable_types = {
42 PackageUploadCustomFormat.DEBIAN_INSTALLER: DebianInstallerUpload,43 PackageUploadCustomFormat.DEBIAN_INSTALLER: DebianInstallerUpload,
43 PackageUploadCustomFormat.DIST_UPGRADER: DistUpgraderUpload,44 PackageUploadCustomFormat.DIST_UPGRADER: DistUpgraderUpload,
45 PackageUploadCustomFormat.DDTP_TARBALL: DdtpTarballUpload,
44 PackageUploadCustomFormat.UEFI: UefiUpload,46 PackageUploadCustomFormat.UEFI: UefiUpload,
45 }47 }
4648
@@ -70,9 +72,6 @@
7072
71 def getKey(self, upload):73 def getKey(self, upload):
72 """Get an indexing key for `upload`."""74 """Get an indexing key for `upload`."""
73 # XXX JeroenVermeulen 2011-08-17, bug=827941: For ddtp
74 # translations tarballs, we'll have to include the component
75 # name as well.
76 custom_format = upload.customformat75 custom_format = upload.customformat
77 series_key = self.extractSeriesKey(76 series_key = self.extractSeriesKey(
78 self.copyable_types[custom_format],77 self.copyable_types[custom_format],
7978
=== modified file 'lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py'
--- lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-06-22 17:26:53 +0000
+++ lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-07-10 11:27:22 +0000
@@ -111,16 +111,22 @@
111111
112 def makeUpload(self, distroseries=None, pocket=None,112 def makeUpload(self, distroseries=None, pocket=None,
113 custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,113 custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
114 version=None, arch=None):114 version=None, arch=None, component=None):
115 """Create a `PackageUploadCustom`."""115 """Create a `PackageUploadCustom`."""
116 if distroseries is None:116 if distroseries is None:
117 distroseries = self.factory.makeDistroSeries()117 distroseries = self.factory.makeDistroSeries()
118 package_name = self.factory.getUniqueString("package")118 package_name = self.factory.getUniqueString("package")
119 if version is None:119 if version is None:
120 version = self.makeVersion()120 version = self.makeVersion()
121 if arch is None:121 if custom_type == PackageUploadCustomFormat.DDTP_TARBALL:
122 arch = self.factory.getUniqueString()122 if component is None:
123 filename = "%s.tar.gz" % '_'.join([package_name, version, arch])123 component = self.factory.getUniqueString()
124 filename = "%s.tar.gz" % "_".join(
125 [package_name, component, version])
126 else:
127 if arch is None:
128 arch = self.factory.getUniqueString()
129 filename = "%s.tar.gz" % "_".join([package_name, version, arch])
124 package_upload = self.factory.makeCustomPackageUpload(130 package_upload = self.factory.makeCustomPackageUpload(
125 distroseries=distroseries, pocket=pocket, custom_type=custom_type,131 distroseries=distroseries, pocket=pocket, custom_type=custom_type,
126 filename=filename)132 filename=filename)
@@ -230,9 +236,6 @@
230 def test_getKey_includes_format_and_architecture(self):236 def test_getKey_includes_format_and_architecture(self):
231 # The key returned by getKey consists of custom upload type,237 # The key returned by getKey consists of custom upload type,
232 # and architecture.238 # and architecture.
233 # XXX JeroenVermeulen 2011-08-17, bug=827941: To support
234 # ddtp-translations uploads, this will have to include the
235 # component name as well.
236 source_series = self.factory.makeDistroSeries()239 source_series = self.factory.makeDistroSeries()
237 upload = self.makeUpload(240 upload = self.makeUpload(
238 source_series, custom_type=PackageUploadCustomFormat.DIST_UPGRADER,241 source_series, custom_type=PackageUploadCustomFormat.DIST_UPGRADER,
@@ -244,6 +247,20 @@
244 )247 )
245 self.assertEqual(expected_key, copier.getKey(upload))248 self.assertEqual(expected_key, copier.getKey(upload))
246249
250 def test_getKey_ddtp_includes_format_and_component(self):
251 # The key returned by getKey for a ddtp-tarball upload consists of
252 # custom upload type, and component.
253 source_series = self.factory.makeDistroSeries()
254 upload = self.makeUpload(
255 source_series, custom_type=PackageUploadCustomFormat.DDTP_TARBALL,
256 component='restricted')
257 copier = CustomUploadsCopier(FakeDistroSeries())
258 expected_key = (
259 PackageUploadCustomFormat.DDTP_TARBALL,
260 'restricted',
261 )
262 self.assertEqual(expected_key, copier.getKey(upload))
263
247 def test_getLatestUploads_indexes_uploads_by_key(self):264 def test_getLatestUploads_indexes_uploads_by_key(self):
248 # getLatestUploads returns a dict of uploads, indexed by keys265 # getLatestUploads returns a dict of uploads, indexed by keys
249 # returned by getKey.266 # returned by getKey.