]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/checks.py
Add option to forbid source and/or binary uploads to a suite
[dak.git] / daklib / checks.py
index b7931f2f6d8e60a2b893f6a304b357fe88cefd31..e17a191e39472b112e50f24a00442da161c65970 100644 (file)
@@ -31,11 +31,12 @@ from daklib.regexes import *
 from daklib.textutils import fix_maintainer, ParseMaintError
 import daklib.lintian as lintian
 import daklib.utils as utils
-from daklib.upload import InvalidHashException
+import daklib.upload
 
 import apt_inst
 import apt_pkg
 from apt_pkg import version_compare
+import datetime
 import errno
 import os
 import subprocess
@@ -56,7 +57,7 @@ class Reject(Exception):
     """exception raised by failing checks"""
     pass
 
-class RejectStupidMaintainerException(Exception):
+class RejectExternalFilesMismatch(Reject):
     """exception raised by failing the external hashes check"""
 
     def __str__(self):
@@ -112,13 +113,11 @@ class SignatureAndHashesCheck(Check):
     def check_replay(self, upload):
         # Use private session as we want to remember having seen the .changes
         # in all cases.
-        session = DBConn().session()
+        session = upload.session
         history = SignatureHistory.from_signed_file(upload.changes)
         r = history.query(session)
         if r is not None:
-            raise Reject('Signature for changes file was already seen at {0}'.format(r.seen))
-        session.add(history)
-        session.commit()
+            raise Reject('Signature for changes file was already seen at {0}.\nPlease refresh the signature of the changes file if you want to upload it again.'.format(r.seen))
         return True
 
     """Check signature of changes and dsc file (if included in upload)
@@ -162,15 +161,32 @@ class SignatureAndHashesCheck(Check):
         try:
             for f in files:
                 f.check(upload.directory)
-        except IOError as e:
-            if e.errno == errno.ENOENT:
-                raise Reject('{0} refers to non-existing file: {1}\n'
-                             'Perhaps you need to include it in your upload?'
-                             .format(filename, os.path.basename(e.filename)))
-            raise
-        except InvalidHashException as e:
+        except daklib.upload.FileDoesNotExist as e:
+            raise Reject('{0}: {1}\n'
+                         'Perhaps you need to include the file in your upload?'
+                         .format(filename, unicode(e)))
+        except daklib.upload.UploadException as e:
             raise Reject('{0}: {1}'.format(filename, unicode(e)))
 
+class SignatureTimestampCheck(Check):
+    """Check timestamp of .changes signature"""
+    def check(self, upload):
+        changes = upload.changes
+
+        now = datetime.datetime.utcnow()
+        timestamp = changes.signature_timestamp
+        age = now - timestamp
+
+        age_max = datetime.timedelta(days=365)
+        age_min = datetime.timedelta(days=-7)
+
+        if age > age_max:
+            raise Reject('{0}: Signature from {1} is too old (maximum age is {2} days)'.format(changes.filename, timestamp, age_max.days))
+        if age < age_min:
+            raise Reject('{0}: Signature from {1} is too far in the future (tolerance is {2} days)'.format(changes.filename, timestamp, abs(age_min.days)))
+
+        return True
+
 class ChangesCheck(Check):
     """Check changes file for syntax errors."""
     def check(self, upload):
@@ -240,16 +256,16 @@ class ExternalHashesCheck(Check):
             return
 
         if ext_size != f.size:
-            raise RejectStupidMaintainerException(f.filename, 'size', f.size, ext_size)
+            raise RejectExternalFilesMismatch(f.filename, 'size', f.size, ext_size)
 
         if ext_md5sum != f.md5sum:
-            raise RejectStupidMaintainerException(f.filename, 'md5sum', f.md5sum, ext_md5sum)
+            raise RejectExternalFilesMismatch(f.filename, 'md5sum', f.md5sum, ext_md5sum)
 
         if ext_sha1sum != f.sha1sum:
-            raise RejectStupidMaintainerException(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
+            raise RejectExternalFilesMismatch(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
 
         if ext_sha256sum != f.sha256sum:
-            raise RejectStupidMaintainerException(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
+            raise RejectExternalFilesMismatch(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
 
     def check(self, upload):
         cnf = Config()
@@ -270,13 +286,41 @@ class ExternalHashesCheck(Check):
 class BinaryCheck(Check):
     """Check binary packages for syntax errors."""
     def check(self, upload):
+        debug_deb_name_postfix = "-dbgsym"
+        # XXX: Handle dynamic debug section name here
+
         for binary in upload.changes.binaries:
             self.check_binary(upload, binary)
 
-        binary_names = set([ binary.control['Package'] for binary in upload.changes.binaries ])
-        for bn in binary_names:
-            if bn not in upload.changes.binary_names:
-                raise Reject('Package {0} is not mentioned in Binary field in changes'.format(bn))
+        binaries = {binary.control['Package']: binary
+                        for binary in upload.changes.binaries}
+
+        for name, binary in binaries.items():
+            if name in upload.changes.binary_names:
+                # Package is listed in Binary field. Everything is good.
+                pass
+            elif daklib.utils.is_in_debug_section(binary.control):
+                # If we have a binary package in the debug section, we
+                # can allow it to not be present in the Binary field
+                # in the .changes file, so long as its name (without
+                # -dbgsym) is present in the Binary list.
+                if not name.endswith(debug_deb_name_postfix):
+                    raise Reject('Package {0} is in the debug section, but '
+                                 'does not end in {1}.'.format(name, debug_deb_name_postfix))
+
+                # Right, so, it's named properly, let's check that
+                # the corresponding package is in the Binary list
+                origin_package_name = name[:-len(debug_deb_name_postfix)]
+                if origin_package_name not in upload.changes.binary_names:
+                    raise Reject(
+                        "Debug package {debug}'s corresponding binary package "
+                        "{origin} is not present in the Binary field.".format(
+                            debug=name, origin=origin_package_name))
+            else:
+                # Someone was a nasty little hacker and put a package
+                # into the .changes that isn't in debian/control. Bad,
+                # bad person.
+                raise Reject('Package {0} is not mentioned in Binary field in changes'.format(name))
 
         return True
 
@@ -284,7 +328,7 @@ class BinaryCheck(Check):
         fn = binary.hashed_file.filename
         control = binary.control
 
-        for field in ('Package', 'Architecture', 'Version', 'Description'):
+        for field in ('Package', 'Architecture', 'Version', 'Description', 'Section'):
             if field not in control:
                 raise Reject('{0}: Missing mandatory field {0}.'.format(fn, field))
 
@@ -345,6 +389,11 @@ class BinaryCheck(Check):
                 except:
                     raise Reject('{0}: APT could not parse {1} field'.format(fn, field))
 
+        # "Multi-Arch: no" breaks wanna-build, #768353
+        multi_arch = control.get("Multi-Arch")
+        if multi_arch == 'no':
+            raise Reject('{0}: Multi-Arch: no support in Debian is broken (#768353)'.format(fn))
+
 class BinaryTimestampCheck(Check):
     """check timestamps of files in binary packages
 
@@ -668,6 +717,9 @@ class NoSourceOnlyCheck(Check):
 
         if not allow_no_arch_indep_uploads \
            and 'all' not in changes.architectures \
+           and 'experimental' not in changes.distributions \
+           and 'unstable' not in changes.distributions \
+           and 'sid' not in changes.distributions \
            and changes.source.package_list.has_arch_indep_packages():
             raise Reject('Uploads not including architecture-independent packages are not allowed.')
 
@@ -747,6 +799,14 @@ class SourceFormatCheck(Check):
         if query.first() is None:
             raise Reject('source format {0} is not allowed in suite {1}'.format(source_format, suite.suite_name))
 
+class SuiteCheck(Check):
+    def per_suite_check(self, upload, suite):
+        if not suite.accept_source_uploads and upload.changes.source is not None:
+            raise Reject('The suite "{0}" does not accept source uploads.'.format(suite.suite_name))
+        if not suite.accept_binary_uploads and len(upload.changes.binaries) != 0:
+            raise Reject('The suite "{0}" does not accept binary uploads.'.format(suite.suite_name))
+        return True
+
 class SuiteArchitectureCheck(Check):
     def per_suite_check(self, upload, suite):
         session = upload.session