Merge lp:~abentley/launchpad/no-write-cwd into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11172
Proposed branch: lp:~abentley/launchpad/no-write-cwd
Merge into: lp:launchpad
Diff against target: 271 lines (+69/-41)
4 files modified
lib/lp/archiveuploader/dscfile.py (+32/-28)
lib/lp/archiveuploader/nascentupload.py (+7/-0)
lib/lp/archiveuploader/tests/test_dscfile.py (+29/-12)
lib/lp/archiveuploader/uploadprocessor.py (+1/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/no-write-cwd
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing
Julian Edwards (community) Approve
Review via email: mp+29965@code.launchpad.net

Commit message

Avoid writing to the to the CWD.

Description of the change

= Summary =
Avoid writing to CWD in the archive uploader.

== Proposed fix ==
Introduce a DSCFile.cleanUp method, so that we can have a temp directory that
lives between unpackAndCheckSource and storeInDatabase.

== Pre-implementation notes ==
Mid-implementation discussion with bigjools

== Implementation details ==
None

== Tests ==
bin/test -t nascent -t archiveuploader

== Demo and Q/A ==
Try doing a sourcepackagerecipe build on staging. It should not die like this:
http://staging.launchpadlibrarian.net/51599092/aRg8XXIxzd43E3UO4chC78epwXK.txt

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/archiveuploader/nascentupload.py
  lib/lp/archiveuploader/uploadprocessor.py
  lib/lp/archiveuploader/dscfile.py
  lib/lp/archiveuploader/tests/test_dscfile.py

/home/abentley/launchpad/no-write-cwd/bin/lint.sh: line 161: pocketlint: command not found

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Looks good! Just a couple of small things:

1. change the indentation at line 41 of the diff as we discussed.
2. from lp.archiveuploader.tests import datadir
  then remove the hardcoded /lib/lp/archiveuploaders/tests/data

Thanks for making the change.

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

Looks like this is in tweak mode.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :

> Looks like this is in tweak mode.

If you're going to say that, please say a bit more.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Jul 19, 2010 at 4:38 PM, Aaron Bentley <email address hidden> wrote:
>> Looks like this is in tweak mode.
>
> If you're going to say that, please say a bit more.

Another reviewer has asked for some changes and for the branch to be
landed after that - what I meant by 'it is in tweak mode' is that it
should have its status set accordingly: it no longer needs review,
rather it needs those changes made and the branch to be landed.

Revision history for this message
Aaron Bentley (abentley) wrote :

> Another reviewer has asked for some changes and for the branch to be
> landed after that - what I meant by 'it is in tweak mode' is that it
> should have its status set accordingly: it no longer needs review,
> rather it needs those changes made and the branch to be landed.

The convention in Launchpad reviews is that approve reviews may be conditional, based on the text of the review. A subsequent "needs fixing" review like yours creates confusion.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Jul 19, 2010 at 5:37 PM, Aaron Bentley <email address hidden> wrote:
>> Another reviewer has asked for some changes and for the branch to be
>> landed after that - what I meant by 'it is in tweak mode' is that it
>> should have its status set accordingly: it no longer needs review,
>> rather it needs those changes made and the branch to be landed.
>
> The convention in Launchpad reviews is that approve reviews may be conditional, based on the text of the review.  A subsequent "needs fixing" review like yours creates confusion.

I'll try to avoid confusion in future. I was perhaps getting a bit
brief - we had a significant number of confusing-status merge
proposals in the queue. Sorry.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archiveuploader/dscfile.py'
--- lib/lp/archiveuploader/dscfile.py 2010-06-25 21:35:15 +0000
+++ lib/lp/archiveuploader/dscfile.py 2010-07-20 14:43:51 +0000
@@ -13,7 +13,7 @@
13 'SignableTagFile',13 'SignableTagFile',
14 'DSCFile',14 'DSCFile',
15 'DSCUploadedFile',15 'DSCUploadedFile',
16 'findAndMoveChangelog',16 'findChangelog',
17 'findCopyright',17 'findCopyright',
18 ]18 ]
1919
@@ -204,6 +204,8 @@
204 else:204 else:
205 self.processSignature()205 self.processSignature()
206206
207 self.unpacked_dir = None
208
207 #209 #
208 # Useful properties.210 # Useful properties.
209 #211 #
@@ -483,18 +485,19 @@
483 "Verifying uploaded source package by unpacking it.")485 "Verifying uploaded source package by unpacking it.")
484486
485 # Get a temporary dir together.487 # Get a temporary dir together.
486 tmpdir = tempfile.mkdtemp()488 self.unpacked_dir = tempfile.mkdtemp()
487489
488 # chdir into it490 # chdir into it
489 cwd = os.getcwd()491 cwd = os.getcwd()
490 os.chdir(tmpdir)492 os.chdir(self.unpacked_dir)
491 dsc_in_tmpdir = os.path.join(tmpdir, self.filename)493 dsc_in_tmpdir = os.path.join(self.unpacked_dir, self.filename)
492494
493 package_files = self.files + [self]495 package_files = self.files + [self]
494 try:496 try:
495 for source_file in package_files:497 for source_file in package_files:
496 os.symlink(source_file.filepath,498 os.symlink(
497 os.path.join(tmpdir, source_file.filename))499 source_file.filepath,
500 os.path.join(self.unpacked_dir, source_file.filename))
498 args = ["dpkg-source", "-sn", "-x", dsc_in_tmpdir]501 args = ["dpkg-source", "-sn", "-x", dsc_in_tmpdir]
499 dpkg_source = subprocess.Popen(args, stdout=subprocess.PIPE,502 dpkg_source = subprocess.Popen(args, stdout=subprocess.PIPE,
500 stderr=subprocess.PIPE)503 stderr=subprocess.PIPE)
@@ -517,8 +520,9 @@
517 # SourcePackageRelease records.520 # SourcePackageRelease records.
518521
519 # Check if 'dpkg-source' created only one directory.522 # Check if 'dpkg-source' created only one directory.
520 temp_directories = [dirname for dirname in os.listdir(tmpdir)523 temp_directories = [
521 if os.path.isdir(dirname)]524 dirname for dirname in os.listdir(self.unpacked_dir)
525 if os.path.isdir(dirname)]
522 if len(temp_directories) > 1:526 if len(temp_directories) > 1:
523 yield UploadError(527 yield UploadError(
524 'Unpacked source contains more than one directory: %r'528 'Unpacked source contains more than one directory: %r'
@@ -528,31 +532,33 @@
528 # name (<sourcename>-<no_epoch(no_revision(version))>).532 # name (<sourcename>-<no_epoch(no_revision(version))>).
529533
530 # Locate both the copyright and changelog files for later processing.534 # Locate both the copyright and changelog files for later processing.
531 for error in findCopyright(self, tmpdir, self.logger):535 for error in findCopyright(self, self.unpacked_dir, self.logger):
532 yield error536 yield error
533537
534 for error in findAndMoveChangelog(self, cwd, tmpdir, self.logger):538 for error in findChangelog(self, self.unpacked_dir, self.logger):
535 yield error539 yield error
536540
537 self.logger.debug("Cleaning up source tree.")541 self.logger.debug("Cleaning up source tree.")
542 self.logger.debug("Done")
543
544 def cleanUp(self):
545 if self.unpacked_dir is None:
546 return
538 try:547 try:
539 shutil.rmtree(tmpdir)548 shutil.rmtree(self.unpacked_dir)
540 except OSError, error:549 except OSError, error:
541 # XXX: dsilvers 2006-03-15: We currently lack a test for this.550 # XXX: dsilvers 2006-03-15: We currently lack a test for this.
542 if errno.errorcode[error.errno] != 'EACCES':551 if errno.errorcode[error.errno] != 'EACCES':
543 yield UploadError(552 raise UploadError(
544 "%s: couldn't remove tmp dir %s: code %s" % (553 "%s: couldn't remove tmp dir %s: code %s" % (
545 self.filename, tmpdir, error.errno))554 self.filename, self.unpacked_dir, error.errno))
546 else:555 else:
547 yield UploadWarning(556 result = os.system("chmod -R u+rwx " + self.unpacked_dir)
548 "%s: Couldn't remove tree, fixing up permissions." %
549 self.filename)
550 result = os.system("chmod -R u+rwx " + tmpdir)
551 if result != 0:557 if result != 0:
552 yield UploadError("chmod failed with %s" % result)558 raise UploadError("chmod failed with %s" % result)
553 shutil.rmtree(tmpdir)559 shutil.rmtree(self.unpacked_dir)
560 self.unpacked_dir = None
554561
555 self.logger.debug("Done")
556562
557 def findBuild(self):563 def findBuild(self):
558 """Find and return the SourcePackageRecipeBuild, if one is specified.564 """Find and return the SourcePackageRecipeBuild, if one is specified.
@@ -733,15 +739,14 @@
733 logger.debug("Copying copyright contents.")739 logger.debug("Copying copyright contents.")
734 dsc_file.copyright = open(copyright_file).read().strip()740 dsc_file.copyright = open(copyright_file).read().strip()
735741
736def findAndMoveChangelog(dsc_file, target_dir, source_dir, logger):742def findChangelog(dsc_file, source_dir, logger):
737 """Find and move any debian/changelog.743 """Find and move any debian/changelog.
738744
739 This function finds the changelog file within the source package and745 This function finds the changelog file within the source package. The
740 moves it to target_dir. The changelog file is later uploaded to the 746 changelog file is later uploaded to the librarian by
741 librarian by DSCFile.storeInDatabase().747 DSCFile.storeInDatabase().
742748
743 :param dsc_file: A DSCFile object where the copyright will be stored.749 :param dsc_file: A DSCFile object where the copyright will be stored.
744 :param target_dir: The directory where the changelog will end up.
745 :param source_dir: The directory where the source was extracted.750 :param source_dir: The directory where the source was extracted.
746 :param logger: A logger object for debug output.751 :param logger: A logger object for debug output.
747 """752 """
@@ -756,9 +761,8 @@
756 return761 return
757762
758 # Move the changelog file out of the package direcotry763 # Move the changelog file out of the package direcotry
759 logger.debug("Found changelog contents; moving to root directory")764 logger.debug("Found changelog")
760 dsc_file.changelog_path = os.path.join(target_dir, "changelog")765 dsc_file.changelog_path = changelog_file
761 shutil.move(changelog_file, dsc_file.changelog_path)
762766
763def check_format_1_0_files(filename, file_type_counts, component_counts,767def check_format_1_0_files(filename, file_type_counts, component_counts,
764 bzip2_count):768 bzip2_count):
765769
=== modified file 'lib/lp/archiveuploader/nascentupload.py'
--- lib/lp/archiveuploader/nascentupload.py 2010-05-27 22:18:16 +0000
+++ lib/lp/archiveuploader/nascentupload.py 2010-07-20 14:43:51 +0000
@@ -839,6 +839,12 @@
839 'Exception while accepting:\n %s' % e, exc_info=True)839 'Exception while accepting:\n %s' % e, exc_info=True)
840 self.do_reject(notify)840 self.do_reject(notify)
841 return False841 return False
842 else:
843 self.cleanUp()
844
845 def cleanUp(self):
846 if self.changes.dsc is not None:
847 self.changes.dsc.cleanUp()
842848
843 def do_reject(self, notify=True):849 def do_reject(self, notify=True):
844 """Reject the current upload given the reason provided."""850 """Reject the current upload given the reason provided."""
@@ -869,6 +875,7 @@
869 self.queue_root.notify(summary_text=self.rejection_message,875 self.queue_root.notify(summary_text=self.rejection_message,
870 changes_file_object=changes_file_object, logger=self.logger)876 changes_file_object=changes_file_object, logger=self.logger)
871 changes_file_object.close()877 changes_file_object.close()
878 self.cleanUp()
872879
873 def _createQueueEntry(self):880 def _createQueueEntry(self):
874 """Return a PackageUpload object."""881 """Return a PackageUpload object."""
875882
=== modified file 'lib/lp/archiveuploader/tests/test_dscfile.py'
--- lib/lp/archiveuploader/tests/test_dscfile.py 2010-07-18 00:26:33 +0000
+++ lib/lp/archiveuploader/tests/test_dscfile.py 2010-07-20 14:43:51 +0000
@@ -7,11 +7,14 @@
77
8import os8import os
99
10from canonical.config import config
11from canonical.launchpad.scripts.logger import QuietFakeLogger
10from canonical.testing.layers import LaunchpadZopelessLayer12from canonical.testing.layers import LaunchpadZopelessLayer
11from lp.archiveuploader.dscfile import (13from lp.archiveuploader.dscfile import (
12 findAndMoveChangelog, findCopyright)14 DSCFile, findChangelog, findCopyright)
13from lp.archiveuploader.nascentuploadfile import UploadError15from lp.archiveuploader.nascentuploadfile import UploadError
14from lp.archiveuploader.tests import mock_logger_quiet16from lp.archiveuploader.tests import datadir, mock_logger_quiet
17from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
15from lp.testing import TestCase, TestCaseWithFactory18from lp.testing import TestCase, TestCaseWithFactory
1619
1720
@@ -27,7 +30,6 @@
27 os.makedirs(self.dir_path)30 os.makedirs(self.dir_path)
28 self.copyright_path = os.path.join(self.dir_path, "copyright")31 self.copyright_path = os.path.join(self.dir_path, "copyright")
29 self.changelog_path = os.path.join(self.dir_path, "changelog")32 self.changelog_path = os.path.join(self.dir_path, "changelog")
30 self.changelog_dest = os.path.join(self.tmpdir, "changelog")
31 self.dsc_file = self.MockDSCFile()33 self.dsc_file = self.MockDSCFile()
3234
33 def testBadDebianCopyright(self):35 def testBadDebianCopyright(self):
@@ -66,8 +68,8 @@
66 dangling symlink in an attempt to try and access files on the system68 dangling symlink in an attempt to try and access files on the system
67 processing the source packages."""69 processing the source packages."""
68 os.symlink("/etc/passwd", self.changelog_path)70 os.symlink("/etc/passwd", self.changelog_path)
69 errors = list(findAndMoveChangelog(71 errors = list(findChangelog(
70 self.dsc_file, self.tmpdir, self.tmpdir, mock_logger_quiet))72 self.dsc_file, self.tmpdir, mock_logger_quiet))
7173
72 self.assertEqual(len(errors), 1)74 self.assertEqual(len(errors), 1)
73 self.assertIsInstance(errors[0], UploadError)75 self.assertIsInstance(errors[0], UploadError)
@@ -82,12 +84,12 @@
82 file.write(changelog)84 file.write(changelog)
83 file.close()85 file.close()
8486
85 errors = list(findAndMoveChangelog(87 errors = list(findChangelog(
86 self.dsc_file, self.tmpdir, self.tmpdir, mock_logger_quiet))88 self.dsc_file, self.tmpdir, mock_logger_quiet))
8789
88 self.assertEqual(len(errors), 0)90 self.assertEqual(len(errors), 0)
89 self.assertEqual(self.dsc_file.changelog_path,91 self.assertEqual(self.dsc_file.changelog_path,
90 self.changelog_dest)92 self.changelog_path)
9193
92 def testOversizedFile(self):94 def testOversizedFile(self):
93 """Test that a file larger than 10MiB will fail.95 """Test that a file larger than 10MiB will fail.
@@ -106,8 +108,8 @@
106 file.write(empty_file)108 file.write(empty_file)
107 file.close()109 file.close()
108110
109 errors = list(findAndMoveChangelog(111 errors = list(findChangelog(
110 self.dsc_file, self.tmpdir, self.tmpdir, mock_logger_quiet))112 self.dsc_file, self.tmpdir, mock_logger_quiet))
111113
112 self.assertIsInstance(errors[0], UploadError)114 self.assertIsInstance(errors[0], UploadError)
113 self.assertEqual(115 self.assertEqual(
@@ -120,12 +122,27 @@
120122
121 layer = LaunchpadZopelessLayer123 layer = LaunchpadZopelessLayer
122124
125 def getDscFile(self, name):
126 dsc_path = datadir(os.path.join('suite', name, name + '.dsc'))
127 class Changes:
128 architectures = ['source']
129 logger = QuietFakeLogger()
130 policy = BuildDaemonUploadPolicy()
131 policy.distroseries = self.factory.makeDistroSeries()
132 policy.archive = self.factory.makeArchive()
133 policy.distro = policy.distroseries.distribution
134 return DSCFile(dsc_path, 'digest', 0, 'main/editors',
135 'priority', 'package', 'version', Changes, policy, logger)
136
123 def test_ReadOnlyCWD(self):137 def test_ReadOnlyCWD(self):
124 """Processing a file should work when cwd is read-only."""138 """Processing a file should work when cwd is read-only."""
125 tempdir = self.useTempDir()139 tempdir = self.useTempDir()
126 dsc_file = self.factory.makeDscFile(tempdir)
127 os.chmod(tempdir, 0555)140 os.chmod(tempdir, 0555)
128 try:141 try:
129 list(dsc_file.unpackAndCheckSource())142 dsc_file = self.getDscFile('bar_1.0-1')
143 try:
144 list(dsc_file.verify())
145 finally:
146 dsc_file.cleanUp()
130 finally:147 finally:
131 os.chmod(tempdir, 0755)148 os.chmod(tempdir, 0755)
132149
=== modified file 'lib/lp/archiveuploader/uploadprocessor.py'
--- lib/lp/archiveuploader/uploadprocessor.py 2010-06-29 14:01:01 +0000
+++ lib/lp/archiveuploader/uploadprocessor.py 2010-07-20 14:43:51 +0000
@@ -293,7 +293,7 @@
293 (distribution, suite_name,293 (distribution, suite_name,
294 archive) = parse_upload_path(relative_path)294 archive) = parse_upload_path(relative_path)
295 except UploadPathError, e:295 except UploadPathError, e:
296 # pick some defaults to create the NascentUploap() object.296 # pick some defaults to create the NascentUpload() object.
297 # We will be rejecting the upload so it doesn matter much.297 # We will be rejecting the upload so it doesn matter much.
298 distribution = getUtility(IDistributionSet)['ubuntu']298 distribution = getUtility(IDistributionSet)['ubuntu']
299 suite_name = None299 suite_name = None