Merge ~cjwatson/launchpad:ddtp-translations-en into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: f506b902811acaee822c61e79387bc1b7907d1a7
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:ddtp-translations-en
Merge into: launchpad:master
Diff against target: 194 lines (+85/-8)
4 files modified
lib/lp/archivepublisher/ddtp_tarball.py (+31/-2)
lib/lp/archivepublisher/model/ftparchive.py (+1/-0)
lib/lp/archivepublisher/publishing.py (+2/-1)
lib/lp/archivepublisher/tests/test_ddtp_tarball.py (+51/-5)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+410320@code.launchpad.net

Commit message

Prevent Translation-en publishing collisions

Description of the change

If the series is configured to do so, the publisher may generate Translation-en files with long descriptions extracted from Packages files. However, it's also possible for DDTP tarballs to include Translation-en files, which then collide with the publisher when extracted and produce either assertion failures or inconsistent Release files depending on which one happened to win. Filter these out.

I fixed a ResourceWarning in passing.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/archivepublisher/ddtp_tarball.py b/lib/lp/archivepublisher/ddtp_tarball.py
index d0f4c60..78e4e3e 100644
--- a/lib/lp/archivepublisher/ddtp_tarball.py
+++ b/lib/lp/archivepublisher/ddtp_tarball.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""The processing of translated packages descriptions (ddtp) tarballs.4"""The processing of translated packages descriptions (ddtp) tarballs.
@@ -16,8 +16,13 @@ __all__ = [
1616
17import os17import os
1818
19from zope.component import getUtility
20
19from lp.archivepublisher.config import getPubConfig21from lp.archivepublisher.config import getPubConfig
20from lp.archivepublisher.customupload import CustomUpload22from lp.archivepublisher.customupload import CustomUpload
23from lp.registry.interfaces.distroseries import IDistroSeriesSet
24from lp.services.features import getFeatureFlag
25from lp.soyuz.enums import ArchivePurpose
2126
2227
23class DdtpTarballUpload(CustomUpload):28class DdtpTarballUpload(CustomUpload):
@@ -59,6 +64,9 @@ class DdtpTarballUpload(CustomUpload):
5964
60 def setTargetDirectory(self, archive, tarfile_path, suite):65 def setTargetDirectory(self, archive, tarfile_path, suite):
61 self.setComponents(tarfile_path)66 self.setComponents(tarfile_path)
67 self.archive = archive
68 self.distro_series, _ = getUtility(IDistroSeriesSet).fromSuite(
69 archive.distribution, suite)
62 pubconf = getPubConfig(archive)70 pubconf = getPubConfig(archive)
63 self.targetdir = os.path.join(71 self.targetdir = os.path.join(
64 pubconf.archiveroot, 'dists', suite, self.component)72 pubconf.archiveroot, 'dists', suite, self.component)
@@ -76,7 +84,28 @@ class DdtpTarballUpload(CustomUpload):
7684
77 def shouldInstall(self, filename):85 def shouldInstall(self, filename):
78 # Ignore files outside of the i18n subdirectory86 # Ignore files outside of the i18n subdirectory
79 return filename.startswith('i18n/')87 if not filename.startswith("i18n/"):
88 return False
89 # apt-ftparchive or the PPA publisher (with slightly different
90 # conditions depending on the archive purpose) may be configured to
91 # create its own Translation-en files. If so, we must take care not
92 # to allow ddtp-tarball custom uploads to collide with those.
93 if (filename == "i18n/Translation-en" or
94 filename.startswith("i18n/Translation-en.")):
95 # Compare with the step C condition in
96 # PublishDistro.publishArchive.
97 if self.archive.purpose in (
98 ArchivePurpose.PRIMARY, ArchivePurpose.COPY):
99 # See FTPArchiveHandler.writeAptConfig.
100 if not self.distro_series.include_long_descriptions:
101 return False
102 else:
103 # See Publisher._writeComponentIndexes.
104 if (not self.distro_series.include_long_descriptions and
105 getFeatureFlag(
106 "soyuz.ppa.separate_long_descriptions")):
107 return False
108 return True
80109
81 def fixCurrentSymlink(self):110 def fixCurrentSymlink(self):
82 # There is no symlink to fix up for DDTP uploads111 # There is no symlink to fix up for DDTP uploads
diff --git a/lib/lp/archivepublisher/model/ftparchive.py b/lib/lp/archivepublisher/model/ftparchive.py
index d56b881..67ba9d5 100644
--- a/lib/lp/archivepublisher/model/ftparchive.py
+++ b/lib/lp/archivepublisher/model/ftparchive.py
@@ -855,6 +855,7 @@ class FTPArchiveHandler:
855 "CACHEINSERT": "",855 "CACHEINSERT": "",
856 "DISTS": os.path.basename(self._config.distsroot),856 "DISTS": os.path.basename(self._config.distsroot),
857 "HIDEEXTRA": "",857 "HIDEEXTRA": "",
858 # Must match DdtpTarballUpload.shouldInstall.
858 "LONGDESCRIPTION":859 "LONGDESCRIPTION":
859 "true" if include_long_descriptions else "false",860 "true" if include_long_descriptions else "false",
860 })861 })
diff --git a/lib/lp/archivepublisher/publishing.py b/lib/lp/archivepublisher/publishing.py
index 708e9de..86ebe1a 100644
--- a/lib/lp/archivepublisher/publishing.py
+++ b/lib/lp/archivepublisher/publishing.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2019 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__all__ = [4__all__ = [
@@ -893,6 +893,7 @@ class Publisher(object):
893 self.log.debug("Generating Sources")893 self.log.debug("Generating Sources")
894894
895 separate_long_descriptions = False895 separate_long_descriptions = False
896 # Must match DdtpTarballUpload.shouldInstall.
896 if (not distroseries.include_long_descriptions and897 if (not distroseries.include_long_descriptions and
897 getFeatureFlag("soyuz.ppa.separate_long_descriptions")):898 getFeatureFlag("soyuz.ppa.separate_long_descriptions")):
898 # If include_long_descriptions is False and the feature flag is899 # If include_long_descriptions is False and the feature flag is
diff --git a/lib/lp/archivepublisher/tests/test_ddtp_tarball.py b/lib/lp/archivepublisher/tests/test_ddtp_tarball.py
index e90e59b..1c31fa3 100644
--- a/lib/lp/archivepublisher/tests/test_ddtp_tarball.py
+++ b/lib/lp/archivepublisher/tests/test_ddtp_tarball.py
@@ -1,4 +1,4 @@
1# Copyright 2012-2018 Canonical Ltd. This software is licensed under the1# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test ddtp-tarball custom uploads.4"""Test ddtp-tarball custom uploads.
@@ -15,6 +15,7 @@ from zope.component import getUtility
15from lp.archivepublisher.config import getPubConfig15from lp.archivepublisher.config import getPubConfig
16from lp.archivepublisher.ddtp_tarball import DdtpTarballUpload16from lp.archivepublisher.ddtp_tarball import DdtpTarballUpload
17from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet17from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
18from lp.services.features.testing import FeatureFixture
18from lp.services.tarfile_helpers import LaunchpadWriteTarFile19from lp.services.tarfile_helpers import LaunchpadWriteTarFile
19from lp.soyuz.enums import ArchivePurpose20from lp.soyuz.enums import ArchivePurpose
20from lp.testing import TestCaseWithFactory21from lp.testing import TestCaseWithFactory
@@ -34,7 +35,9 @@ class TestDdtpTarball(TestCaseWithFactory):
34 db_pubconf.root_dir = six.ensure_text(self.temp_dir)35 db_pubconf.root_dir = six.ensure_text(self.temp_dir)
35 self.archive = self.factory.makeArchive(36 self.archive = self.factory.makeArchive(
36 distribution=self.distro, purpose=ArchivePurpose.PRIMARY)37 distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
37 self.suite = "distroseries"38 self.distroseries = self.factory.makeDistroSeries(
39 distribution=self.distro)
40 self.suite = self.distroseries.name
38 # CustomUpload.installFiles requires a umask of 0o022.41 # CustomUpload.installFiles requires a umask of 0o022.
39 old_umask = os.umask(0o022)42 old_umask = os.umask(0o022)
40 self.addCleanup(os.umask, old_umask)43 self.addCleanup(os.umask, old_umask)
@@ -58,10 +61,14 @@ class TestDdtpTarball(TestCaseWithFactory):
58 def test_basic(self):61 def test_basic(self):
59 # Processing a simple correct tar file works.62 # Processing a simple correct tar file works.
60 self.openArchive("20060728")63 self.openArchive("20060728")
61 self.tarfile.add_file("i18n/Translation-de", b"")64 names = (
65 "Translation-en", "Translation-en.xz",
66 "Translation-de", "Translation-de.xz")
67 for name in names:
68 self.tarfile.add_file(os.path.join("i18n", name), b"")
62 self.process()69 self.process()
63 self.assertTrue(os.path.exists(70 for name in names:
64 self.getTranslationsPath("Translation-de")))71 self.assertTrue(os.path.exists(self.getTranslationsPath(name)))
6572
66 def test_ignores_empty_directories(self):73 def test_ignores_empty_directories(self):
67 # Empty directories in the tarball are not extracted.74 # Empty directories in the tarball are not extracted.
@@ -92,6 +99,43 @@ class TestDdtpTarball(TestCaseWithFactory):
92 with open(self.getTranslationsPath("Translation-ca")) as ca_file:99 with open(self.getTranslationsPath("Translation-ca")) as ca_file:
93 self.assertEqual("ca", ca_file.read())100 self.assertEqual("ca", ca_file.read())
94101
102 def test_does_not_collide_with_publisher_primary(self):
103 # If the publisher is configured to generate its own Translations-en
104 # file (for the apt-ftparchive case), then colliding entries in DDTP
105 # tarballs are not extracted.
106 self.distroseries.include_long_descriptions = False
107 self.openArchive("20060728")
108 en_names = ("Translation-en", "Translation-en.xz")
109 de_names = ("Translation-de", "Translation-de.xz")
110 for name in en_names + de_names:
111 self.tarfile.add_file(os.path.join("i18n", name), b"")
112 self.process()
113 for name in en_names:
114 self.assertFalse(os.path.exists(self.getTranslationsPath(name)))
115 for name in de_names:
116 self.assertTrue(os.path.exists(self.getTranslationsPath(name)))
117
118 def test_does_not_collide_with_publisher_ppa(self):
119 # If the publisher is configured to generate its own Translations-en
120 # file (for the PPA case), then colliding entries in DDTP
121 # tarballs are not extracted.
122 self.archive = self.factory.makeArchive(
123 distribution=self.distro, purpose=ArchivePurpose.PPA)
124 self.useFixture(FeatureFixture({
125 "soyuz.ppa.separate_long_descriptions": "on",
126 }))
127 self.distroseries.include_long_descriptions = False
128 self.openArchive("20060728")
129 en_names = ("Translation-en", "Translation-en.xz")
130 de_names = ("Translation-de", "Translation-de.xz")
131 for name in en_names + de_names:
132 self.tarfile.add_file(os.path.join("i18n", name), b"")
133 self.process()
134 for name in en_names:
135 self.assertFalse(os.path.exists(self.getTranslationsPath(name)))
136 for name in de_names:
137 self.assertTrue(os.path.exists(self.getTranslationsPath(name)))
138
95 def test_breaks_hard_links(self):139 def test_breaks_hard_links(self):
96 # Our archive uses dsync to replace identical files with hard links140 # Our archive uses dsync to replace identical files with hard links
97 # in order to save some space. tarfile.extract overwrites141 # in order to save some space. tarfile.extract overwrites
@@ -133,6 +177,8 @@ class TestDdtpTarball(TestCaseWithFactory):
133 # getSeriesKey extracts the component from an upload's filename.177 # getSeriesKey extracts the component from an upload's filename.
134 self.openArchive("20060728")178 self.openArchive("20060728")
135 self.assertEqual("main", DdtpTarballUpload.getSeriesKey(self.path))179 self.assertEqual("main", DdtpTarballUpload.getSeriesKey(self.path))
180 self.tarfile.close()
181 self.buffer.close()
136182
137 def test_getSeriesKey_returns_None_on_mismatch(self):183 def test_getSeriesKey_returns_None_on_mismatch(self):
138 # getSeriesKey returns None if the filename does not match the184 # getSeriesKey returns None if the filename does not match the

Subscribers

People subscribed via source and target branches

to status/vote changes: