Install importable modules, and symlinks for command programs.
[dput.git] / dput.py
blobf7c87dd101721ad82ac91a4246234ef124d26709
1 #! /usr/bin/python2
2 # -*- coding: utf-8; -*-
4 # dput.py
5 # Part of ‘dput’, a Debian package upload toolkit.
7 # Copyright © 2015 Ben Finney <ben+debian@benfinney.id.au>
8 # Copyright © 2008–2013 Y Giridhar Appaji Nag <appaji@debian.org>
9 # Copyright © 2006–2008 Thomas Viehmann <tv@beamnet.de>
10 # Copyright © 2000–2005 Christian Kurz <shorty@debian.org>
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 """ dput — Debian package upload tool. """
28 import os
29 import os.path
30 import sys
31 import string
32 import re
33 import glob
34 import signal
35 import pwd
36 import stat
37 import rfc822
38 from hashlib import md5, sha1
39 import importlib
40 import pkgutil
42 # Now import our modules
43 import ConfigParser
45 app_library_path = "/usr/share/dput"
46 sys.path.insert(0, app_library_path)
47 from helper import dputhelper
50 dput_version = "dput 0.9.6"
52 config = None
53 upload_methods = {}
54 files_to_upload = []
55 simulate = unsigned_upload = delay_upload = 0
56 debug = dinstall = check_only = 0
57 config_file = ''
60 def import_upload_functions():
61 """ Import files from the /usr/share/dput and make them available. """
62 package_name = "methods"
63 modules_path = os.path.join(app_library_path, package_name)
64 modules_found = [
65 name for (__, name, ispkg) in
66 pkgutil.iter_modules([modules_path])
67 if not ispkg]
68 if debug:
69 print "D: modules_found: %r" % modules_found
70 for module_name in modules_found:
71 module = importlib.import_module("{package}.{module}".format(
72 package=package_name, module=module_name))
73 if debug:
74 print "D: Module: %s (%r)" % (module_name, module)
75 method_name = module_name
76 if debug:
77 print "D: Method name: %s" % method_name
79 register_upload_function(method_name, module.upload)
82 def register_upload_function(name, function):
83 """ Add function to upload_methods dictionary using key of name. """
84 global upload_methods
86 upload_methods[name] = function
89 def parse_changes(chg_fd):
90 """ Parse the changes file. """
91 check = chg_fd.read(5)
92 if check != '-----':
93 chg_fd.seek(0)
94 else:
95 # found a PGP header, gonna ditch the next 3 lines
96 # eat the rest of the line
97 chg_fd.readline()
98 # Hash: SHA1
99 chg_fd.readline()
100 # empty line
101 chg_fd.readline()
102 if not chg_fd.readline().find('Format') != -1:
103 chg_fd.readline()
104 changes = rfc822.Message(chg_fd)
105 for a in changes.dict['files'].split('\n'):
106 if len(a.split()) != 5:
107 sys.stderr.write("Invalid Files line in .changes:\n %s\n" % a)
108 sys.exit(1)
109 return changes
112 def read_configs(extra_config, debug):
113 """ Read configuration settings from config files.
115 Read configs in this order:
116 * If specified on the command line, only read `extra_config`.
117 * Otherwise, read ‘/etc/dput.cf’ then ‘~/.dput.cf’.
118 The config parser will layer values.
121 global config
123 config = ConfigParser.ConfigParser()
125 config.set('DEFAULT', 'login', 'username')
126 config.set('DEFAULT', 'method', 'scp')
127 config.set('DEFAULT', 'hash', 'md5')
128 config.set('DEFAULT', 'allow_unsigned_uploads', '0')
129 config.set('DEFAULT', 'allow_dcut', '0')
130 config.set('DEFAULT', 'distributions', '')
131 config.set('DEFAULT', 'allowed_distributions', '')
132 config.set('DEFAULT', 'run_lintian', '0')
133 config.set('DEFAULT', 'run_dinstall', '0')
134 config.set('DEFAULT', 'check_version', '0')
135 config.set('DEFAULT', 'scp_compress', '0')
136 config.set('DEFAULT', 'default_host_main', '')
137 config.set('DEFAULT', 'post_upload_command', '')
138 config.set('DEFAULT', 'pre_upload_command', '')
139 config.set('DEFAULT', 'ssh_config_options', '')
140 config.set('DEFAULT', 'passive_ftp', '1')
141 config.set('DEFAULT', 'progress_indicator', '0')
142 config.set('DEFAULT', 'delayed', '')
144 if extra_config:
145 config_files = (extra_config,)
146 else:
147 config_files = ('/etc/dput.cf', os.path.expanduser("~/.dput.cf"))
148 fd = None
149 for config_file in config_files:
150 try:
151 fd = open(config_file)
152 except IOError as e:
153 if debug:
154 sys.stderr.write("%s: %s, skipping\n" % (e[1], config_file))
155 continue
156 if debug:
157 print "D: Parsing Configuration File %s" % config_file
158 try:
159 config.readfp(fd)
160 except ConfigParser.ParsingError as e:
161 sys.stderr.write("Error parsing config file:\n%s\n" % str(e))
162 sys.exit(1)
163 fd.close()
164 if fd is None:
165 sys.stderr.write(
166 "Error: Could not open any configfile, tried %s\n"
167 % (', '.join(config_files)))
168 sys.exit(1)
169 # only check for fqdn and incoming dir, rest have reasonable defaults
170 error = 0
171 for section in config.sections():
172 if config.get(section, 'method') == 'local':
173 config.set(section, 'fqdn', 'localhost')
174 if (
175 not config.has_option(section, 'fqdn') and
176 config.get(section, 'method') != 'local'):
177 sys.stderr.write(
178 "Config error: %s must have a fqdn set\n" % section)
179 error = 1
180 if not config.has_option(section, 'incoming'):
181 sys.stderr.write(
182 "Config error: %s must have an incoming directory set\n"
183 % section)
184 error = 1
185 if error:
186 sys.exit(1)
189 hexStr = string.hexdigits
192 def hexify_string(string):
193 """ Convert a hex string into readable characters. """
194 char = ''
195 for c in string:
196 char = char + hexStr[(ord(c) >> 4) & 0xF] + hexStr[ord(c) & 0xF]
197 return char
200 def checksum_test(filename, hash):
201 """ Generate a checksum for a file.
203 Currently supports md5, sha1. ripemd may come in the future.
206 try:
207 file_to_test = open(filename, 'r')
208 except IOError:
209 print "Can't open %s" % filename
210 sys.exit(1)
212 if hash == 'md5':
213 hash_type = md5
214 else:
215 hash_type = sha1
217 check_obj = hash_type()
219 while 1:
220 data = file_to_test.read(65536)
221 if len(data) == 0:
222 break
223 check_obj.update(data)
225 file_to_test.close()
226 checksum = hexify_string(check_obj.digest())
228 return checksum
231 def check_signature(filename):
232 """ Verify the GnuPG signature on a file. """
233 if os.access(filename, os.R_OK):
234 if os.access("/usr/bin/gpg", os.X_OK):
235 stream = os.popen(
236 "/usr/bin/gpg --status-fd 1 --verify --batch %s"
237 % filename).read()
238 if stream.count('[GNUPG:] GOODSIG'):
239 print "Good signature on %s." % filename
240 elif stream.count('[GNUPG:] BADSIG'):
241 print "Bad signature on %s." % filename
242 sys.exit(1)
243 elif stream.count('[GNUPG:] ERRSIG'):
244 print "Error verifying signature on %s." % filename
245 sys.exit(1)
246 elif stream.count('[GNUPG:] NODATA'):
247 print "No signature on %s." % filename
248 sys.exit(1)
249 else:
250 print "Error in finding signature verification status."
251 else:
252 print "Can't verify signature on %s without GnuPG" % filename
253 print "If you are still using PGP, please read this:"
254 print "http://www.gnupg.org/gph/en/pgp2x.html"
255 sys.exit(1)
256 else:
257 print "Can't read %s" % filename
258 sys.exit(1)
261 def check_upload_variant(changes, debug):
262 """ Check if this is a binary_upload only or not. """
263 binary_upload = 0
264 if 'architecture' in changes.dict:
265 arch = changes.dict['architecture']
266 if debug:
267 print "D: Architecture: %s" % arch
268 if arch.find('source') < 0:
269 if debug:
270 print "D: Doing a binary upload only."
271 binary_upload = 1
272 return binary_upload
275 def verify_signature(
276 host, changes_file, dsc_file, check_only, debug,
277 unsigned_upload, binary_upload):
278 """ Check the signature on the two files given via function call. """
280 if debug:
281 print "D: .changes-File: %s" % changes_file
282 print "D: .dsc-File: %s" % dsc_file
283 if ((check_only or config.getboolean(host, 'allow_unsigned_uploads') == 0)
284 and unsigned_upload == 0):
285 print "Checking signature on .changes"
286 check_signature(changes_file)
287 if not binary_upload:
288 print "Checking signature on .dsc"
289 check_signature(dsc_file)
292 def source_check(changes, debug):
293 """ Check if a source tarball has to be included in the package or not. """
294 include_orig = include_tar = 0
295 if 'version' in changes.dict:
296 version = changes.dict['version']
297 if debug:
298 print "D: Package Version: %s" % version
299 # versions with a dash in them are for non-native only
300 if version.find('-') == -1:
301 # debian native
302 include_tar = 1
303 else:
304 if version.find(':') > 0:
305 if debug:
306 print "D: Epoch found"
307 epoch, version = version.split(':', 1)
308 pos = version.rfind('-')
309 upstream_version = version[0:pos]
310 debian_version = version[pos + 1:]
311 if debug:
312 print "D: Upstream Version: %s" % upstream_version
313 print "D: Debian Version: %s" % debian_version
314 if (
315 debian_version == '0.1' or debian_version == '1'
316 or debian_version == '1.1'):
317 include_orig = 1
318 else:
319 include_tar = 1
320 return (include_orig, include_tar)
323 def verify_files(
324 path, filename, host, check_only, check_version,
325 unsigned_upload, debug):
326 """ Run some tests on the files to verify that they are in good shape. """
328 file_seen = include_orig_tar_gz = include_tar_gz = binary_only = 0
330 name_of_file = filename
332 change_file = os.path.join(path, name_of_file)
334 if debug:
335 print "D: Validating contents of changes file %s" % change_file
336 try:
337 chg_fd = open(change_file, 'r')
338 except IOError:
339 print "Can't open %s" % change_file
340 sys.exit(1)
341 changes = parse_changes(chg_fd)
342 chg_fd.close
344 # Find out if it's a binary only upload or not
345 binary_upload = check_upload_variant(changes, debug)
347 if binary_upload:
348 dsc_file = ''
349 else:
350 dsc_file = None
351 for file in changes.dict['files'].split('\n'):
352 # filename only
353 filename = string.split(file)[4]
354 if filename.find('.dsc') != -1:
355 if debug:
356 print "D: dsc-File: %s" % filename
357 dsc_file = os.path.join(path, filename)
358 if not dsc_file:
359 sys.stderr.write("Error: no dsc file found in sourceful upload\n")
360 sys.exit(1)
362 # Run the check to verify that the package has been tested.
363 try:
364 if config.getboolean(host, 'check_version') == 1 or check_version:
365 version_check(path, changes, debug)
366 except ConfigParser.NoSectionError as e:
367 sys.stderr.write("Error in config file:\n%s\n" % str(e))
368 sys.exit(1)
370 # Verify the signature of the maintainer
371 verify_signature(
372 host, change_file, dsc_file, check_only, debug,
373 unsigned_upload, binary_upload)
375 # Check the sources
376 (include_orig_tar_gz, include_tar_gz) = source_check(changes, debug)
378 # Check md5sum and the size
379 file_list = changes.dict['files'].split('\n')
380 hash_to_use = config.get('DEFAULT', 'hash')
381 for line in file_list:
382 (check_sum, size, section, priority, file) = line.split()
383 file_to_upload = os.path.join(path, file)
384 if debug:
385 print "D: File to upload: %s" % file_to_upload
386 if checksum_test(file_to_upload, hash_to_use) != check_sum:
387 if debug:
388 print "D: Checksum from .changes: %s" % check_sum
389 print (
390 "D: Generated Checksum: %s" %
391 checksum_test(file_to_upload, hash_to_use))
392 print "Checksum doesn't match for %s" % file_to_upload
393 sys.exit(1)
394 else:
395 if debug:
396 print "D: Checksum for %s is fine" % file_to_upload
397 if os.stat(file_to_upload)[stat.ST_SIZE] != int(size):
398 if debug:
399 print "D: size from .changes: %s" % size
400 print (
401 "D: calculated size: %s" %
402 os.stat(file_to_upload)[stat.ST_SIZE])
403 print "size doesn't match for %s" % file_to_upload
405 files_to_upload.append(file_to_upload)
407 # Check filenames
408 for file in files_to_upload:
409 if file[-12:] == '.orig.tar.gz' and not include_orig_tar_gz:
410 if debug:
411 print "D: Filename: %s" % file
412 print "D: Suffix: %s" % file[-12:]
413 print (
414 "Package includes an .orig.tar.gz file although"
415 " the debian revision suggests")
416 print (
417 "that it might not be required."
418 " Multiple uploads of the .orig.tar.gz may be")
419 print "rejected by the upload queue management software."
420 elif (
421 file[-7:] == '.tar.gz' and not include_tar_gz
422 and not include_orig_tar_gz):
423 if debug:
424 print "D: Filename: %s" % file
425 print "D: Suffix: %s" % file[-7:]
426 print (
427 "Package includes a .tar.gz file although"
428 " the version suggests that it might")
429 print (
430 "not be required."
431 " Multiple uploads of the .tar.gz may be rejected by the")
432 print "upload queue management software."
434 distribution = changes.get('distribution')
435 allowed_distributions = config.get(host, 'allowed_distributions')
436 if distribution and allowed_distributions:
437 if debug:
438 print (
439 "D: Checking: distribution %s matches %s"
440 % (distribution, allowed_distributions))
441 if not re.match(allowed_distributions, distribution):
442 raise dputhelper.DputUploadFatalException(
443 "Error: uploading files for distribution %s to %s"
444 " not allowed."
445 % (distribution, host))
447 if debug:
448 print "D: File to upload: %s" % change_file
449 files_to_upload.append(change_file)
452 def print_config(config, debug):
453 """ Print the configuration and exit. """
454 print
455 config.write(sys.stdout)
456 print
459 def create_upload_file(package, host, path, files_to_upload, debug):
460 """ Write a logfile of the upload and call it ‘.upload’. """
462 # only need first part
463 base = os.path.splitext(package)[0]
464 logfile_name = os.path.join(path, base + '.' + host + '.upload')
465 if config.get(host, 'method') == 'local':
466 fqdn = 'localhost'
467 else:
468 fqdn = config.get(host, 'fqdn')
469 if debug:
470 print "D: Writing logfile: %s" % logfile_name
471 try:
472 if os.access(logfile_name, os.R_OK):
473 logfile_fd = open(logfile_name, 'a')
474 else:
475 logfile_fd = open(logfile_name, 'w')
476 except IOError:
477 sys.stderr.write("Could not write %s\n" % logfile_name)
478 sys.exit(1)
480 for file in files_to_upload:
481 entry_for_logfile = (
482 'Successfully uploaded ' + os.path.basename(file) +
483 ' to ' + fqdn + ' for ' + host + '.\n')
484 logfile_fd.write(entry_for_logfile)
485 logfile_fd.close()
488 def run_lintian_test(changes_file):
489 """ Run lintian on the changes file and stop if it finds errors. """
491 if os.access(changes_file, os.R_OK):
492 if os.access("/usr/bin/lintian", os.R_OK):
493 old_signal = signal.signal(signal.SIGPIPE, signal.SIG_DFL)
494 print "Package is now being checked with lintian."
495 if dputhelper.spawnv(
496 os.P_WAIT, "/usr/bin/lintian",
497 ['lintian', '-i', changes_file]):
498 print
499 print (
500 "Lintian says this package is not compliant" +
501 " with the current policy.")
502 print "Please check the current policy and your package."
503 print "Also see lintian documentation about overrides."
504 sys.exit(1)
505 else:
506 signal.signal(signal.SIGPIPE, old_signal)
507 return 0
508 else:
509 print "lintian is not installed, skipping package test."
510 else:
511 print "Can't read %s" % changes_file
512 sys.exit(1)
515 def guess_upload_host(path, filename):
516 """ Guess the host where the package should be uploaded to.
518 This is based on information from the changes file.
521 non_us = 0
522 distribution = ""
523 dist_re = re.compile(r'^Distribution: (.*)')
525 name_of_file = filename
526 changes_file = os.path.join(path, name_of_file)
528 try:
529 changes_file_fd = open(changes_file, 'r')
530 except IOError:
531 print "Can't open %s" % changes_file
532 sys.exit(1)
533 lines = changes_file_fd.readlines()
534 for line in lines:
535 match = dist_re.search(line)
536 if match:
537 distribution = match.group(1)
539 # Try to guess a host based on the Distribution: field
540 if distribution:
541 for section in config.sections():
542 host_dists = config.get(section, 'distributions')
543 if not host_dists:
544 continue
545 for host_dist in host_dists.split(','):
546 if distribution == host_dist.strip():
547 if debug:
548 print (
549 "D: guessing host %s based on distribution %s"
550 % (section, host_dist))
551 return section
553 if len(config.get('DEFAULT', 'default_host_main')) != 0:
554 print "Trying to upload package to %s" % config.get(
555 'DEFAULT', 'default_host_main')
556 return config.get('DEFAULT', 'default_host_main')
557 else:
558 print "Trying to upload package to ftp-master (ftp.upload.debian.org)"
559 return "ftp-master"
562 def dinstall_caller(filename, host, login, incoming, debug):
563 """ Run dinstall in test-mode and present the output to the user.
565 This is so the user can see if the package would be installed
566 or not.
569 command = [
570 'ssh', '%s@%s' % (login, config.get(host, 'fqdn')),
571 'cd', '%s' % incoming,
572 ';', 'dinstall', '-n', '%s' % filename]
573 if debug:
574 print "D: Logging into %s@%s:%s" % (login, host, incoming)
575 print "D: dinstall -n %s" % filename
576 if config.getboolean(host, 'run_dinstall') == 1 or dinstall:
577 if dputhelper.spawnv(os.P_WAIT, '/usr/bin/ssh', command):
578 print (
579 "Error occured while trying to connect, or while " +
580 "attempting to run dinstall.")
581 sys.exit(1)
584 def version_check(path, changes, debug):
585 """ Check if the caller has installed the package also on his system.
587 This is for testing purposes before uploading it. If not, we
588 reject the upload.
591 files_to_check = []
593 # Get arch
594 (dpkg_stdin, dpkg_stdout, dpkg_stderr) = os.popen3(
595 'dpkg --print-architecture')
596 dpkg_stdin.close()
597 dpkg_architecture = dpkg_stdout.read().strip()
598 dpkg_stdout.close()
599 dpkg_stderr_output = dpkg_stderr.read()
600 dpkg_stderr.close()
601 if debug and dpkg_stderr_output:
602 print "D: dpkg-architecture stderr output:", repr(dpkg_stderr_output)
603 if debug:
604 print "D: detected architecture: '%s'" % dpkg_architecture
606 # Get filenames of deb files:
607 for file in changes.dict['files'].split('\n'):
608 filename = os.path.join(path, string.split(file)[4])
609 if filename.endswith('.deb'):
610 if debug:
611 print "D: Debian Package: %s" % filename
612 (dpkg_stdin, dpkg_stdout, dpkg_stderr) = os.popen3(
613 'dpkg --field %s' % filename)
614 dpkg_stdin.close()
615 dpkg_output = rfc822.Message(dpkg_stdout)
616 dpkg_stdout.close()
617 dpkg_stderr_output = dpkg_stderr.read()
618 dpkg_stderr.close()
619 if debug and dpkg_stderr_output:
620 print "D: dpkg stderr output:", repr(dpkg_stderr_output)
621 if (
622 dpkg_architecture
623 and dpkg_output['architecture'] not in [
624 'all', dpkg_architecture]):
625 if debug:
626 print (
627 "D: not install-checking %s due to arch mismatch"
628 % filename)
629 else:
630 package_name = dpkg_output['package']
631 version_number = dpkg_output['version']
632 if debug:
633 print "D: Package to Check: %s" % package_name
634 if debug:
635 print "D: Version to Check: %s" % version_number
636 files_to_check.append((package_name, version_number))
638 for file, version_to_check in files_to_check:
639 if debug:
640 print "D: Name of Package: %s" % file
641 dpkg_stdin, dpkg_stdout, dpkg_stderr = os.popen3('dpkg -s %s' % file)
642 dpkg_stdin.close()
643 dpkg_output = rfc822.Message(dpkg_stdout)
644 dpkg_stdout.close()
645 dpkg_stderr_output = dpkg_stderr.read()
646 dpkg_stderr.close()
647 if debug and dpkg_stderr_output:
648 print "D: dpkg stderr output:", repr(dpkg_stderr_output)
649 if 'version' in dpkg_output:
650 installed_version = dpkg_output.dict['version']
651 if debug:
652 print "D: Installed-Version: %s" % installed_version
653 if debug:
654 print (
655 "D: Check-Version: %s" % version_to_check)
656 if installed_version != version_to_check:
657 print (
658 "Package to upload is not installed, but it appears " +
659 "you have an older version installed.")
660 else:
661 print "Uninstalled Package. Test it before uploading it."
662 sys.exit(1)
665 def execute_command(host, debug, type):
666 """ Run a command that the user-defined in the config_file. """
668 lookup_command = type + '_upload_command'
669 if debug:
670 print "D: Command: %s" % config.get(host, lookup_command)
671 if os.system(config.get(host, lookup_command)):
672 raise dputhelper.DputUploadFatalException(
673 "Error: %s upload command failed." % type)
676 def check_upload_logfile(
677 changes_file, host, check_only,
678 call_lintian, force_upload, debug):
679 """ Check if the user already put this package on the specified host. """
681 uploaded = 0
682 upload_logfile = changes_file[:-8] + '.' + host + '.upload'
683 if not check_only and not force_upload:
684 if not os.path.exists(upload_logfile):
685 return
686 try:
687 fd_logfile = open(upload_logfile)
688 except IOError:
689 print "Couldn't open %s" % upload_logfile
690 sys.exit(1)
691 for line in fd_logfile.readlines():
692 if config.get(host, 'method') == 'local':
693 fqdn = 'localhost'
694 else:
695 fqdn = config.get(host, 'fqdn')
696 if line.find(fqdn) != -1:
697 uploaded = 1
698 if uploaded:
699 print (
700 "Package has already been uploaded to %s on %s"
701 % (host, fqdn))
702 print "Nothing more to do for %s" % changes_file
703 sys.exit(0)
705 # Help Message to print
706 USAGE = """Usage: dput [options] [host] <package(s).changes>
707 Supported options (see man page for long forms):
708 -c: Config file to parse.
709 -d: Enable debug messages.
710 -D: Run dinstall after upload.
711 -e: Upload to a delayed queue. Takes an argument from 0 to 15.
712 -f: Force an upload.
713 -h: Display this help message.
714 -H: Display a list of hosts from the config file.
715 -l: Run lintian before upload.
716 -U: Do not write a .upload file after uploading.
717 -o: Only check the package.
718 -p: Print the configuration.
719 -P: Use passive mode for ftp uploads.
720 -s: Simulate the upload only.
721 -u: Don't check GnuPG signature.
722 -v: Display version information.
723 -V: Check the package version and then upload it.
727 def main():
728 """ Main function, no further comment needed. :) """
730 global simulate
731 global debug
732 global check_only
733 global dinstall
734 global unsigned_upload
735 global config_file
736 global delay_upload
738 check_version = config_print = force_upload = 0
739 call_lintian = no_upload_log = config_host_list = 0
740 ftp_passive_mode = 0
741 preferred_host = ''
743 # Parse Command Line Options.
744 (opts, args) = dputhelper.getopt(
745 sys.argv[1:],
746 'c:dDe:fhHlUopPsuvV', [
747 'debug', 'dinstall', 'check-only',
748 'check-version', 'config=', 'force', 'help',
749 'host-list', 'lintian', 'no-upload-log',
750 'passive', 'print', 'simulate', 'unchecked',
751 'delayed=', 'version'])
752 for option, arg in opts:
753 if option in ('-h', '--help'):
754 print USAGE
755 return
756 elif option in ('-v', '--version'):
757 print dput_version
758 return
759 elif option in ('-d', '--debug'):
760 debug = 1
761 elif option in ('-D', '--dinstall'):
762 dinstall = 1
763 elif option in ('-c', '--config'):
764 config_file = arg
765 elif option in ('-f', '--force'):
766 force_upload = 1
767 elif option in ('-H', '--host-list'):
768 config_host_list = 1
769 elif option in ('-l', '--lintian'):
770 call_lintian = 1
771 elif option in ('-U', '--no-upload-log'):
772 no_upload_log = 1
773 elif option in ('-o', '--check-only'):
774 check_only = 1
775 elif option in ('-p', '--print'):
776 config_print = 1
777 elif option in ('-P', '--passive'):
778 ftp_passive_mode = 1
779 elif option in ('-s', '--simulate'):
780 simulate = 1
781 elif option in ('-u', '--unchecked'):
782 unsigned_upload = 1
783 elif option in ('-e', '--delayed'):
784 if arg in map(str, range(16)):
785 delay_upload = arg
786 else:
787 print (
788 "Incorrect delayed argument,"
789 " dput only understands 0 to 15.")
790 sys.exit(1)
791 elif option in ('-V', '--check_version'):
792 check_version = 1
794 # Always print the version number in the debug output
795 # so that in case of bugreports, we know which version
796 # the user has installed
797 if debug:
798 print "D: %s" % dput_version
800 # Try to get the login from the enviroment
801 if 'USER' in os.environ:
802 login = os.environ['USER']
803 if debug:
804 print "D: Login: %s" % login
805 else:
806 print "$USER not set, will use login information."
807 # Else use the current username
808 login = pwd.getpwuid(os.getuid())[0]
809 if debug:
810 print "D: User-ID: %s" % os.getuid()
811 print "D: Login: %s" % login
813 # Start Config File Parsing.
814 read_configs(config_file, debug)
816 if config_print:
817 print_config(config, debug)
818 sys.exit(0)
820 if config_host_list:
821 print
822 print "Default Method: %s" % config.get('DEFAULT', 'method')
823 print
824 for section in config.sections():
825 distributions = ""
826 if config.get(section, 'distributions'):
827 distributions = (
828 ", distributions: %s" %
829 config.get(section, 'distributions'))
830 print "%s => %s (Upload method: %s%s)" % (
831 section,
832 config.get(section, 'fqdn'), config.get(section, 'method'),
833 distributions)
834 print
835 sys.exit(0)
837 # Process further command line options.
838 if len(args) == 0:
839 print "No package or host has been provided, see dput -h"
840 sys.exit(0)
841 elif len(args) == 1 and not check_only:
842 package_to_upload = args[0:]
843 else:
844 if not check_only:
845 if debug:
846 print "D: Checking if a host was named on the command line."
847 if config.has_section(args[0]):
848 if debug:
849 print "D: Host %s found in config" % args[0]
850 # Host was also named, so only the rest will be a list
851 # of packages to upload.
852 preferred_host = args[0]
853 package_to_upload = args[1:]
854 elif (
855 not config.has_section(args[0])
856 and not args[0].endswith('.changes')):
857 sys.stderr.write("No host %s found in config\n" % args[0])
858 if args[0] == 'gluck_delayed':
859 sys.stderr.write("""
860 The delayed upload queue has been moved back to
861 ftp-master (aka ftp.upload.debian.org).
862 """)
863 sys.exit(1)
864 else:
865 if debug:
866 print "D: No host named on command line."
867 # Only packages have been named on the command line.
868 preferred_host = ''
869 package_to_upload = args[0:]
870 else:
871 if debug:
872 print "D: Checking for the package name."
873 if config.has_section(args[0]):
874 print "D: Host %s found in config." % args[0]
875 preferred_host = args[0]
876 package_to_upload = args[1:]
877 elif not config.has_section(args[0]):
878 print "D: No host %s found in config" % args[0]
879 package_to_upload = args[0:]
881 # Now Import the Upload functions
882 import_upload_functions()
884 # Run the same checks for all packages that have been given on
885 # the command line
886 for package_name in package_to_upload:
887 # Check that a .changes file was given on the command line
888 # and no matching .upload file exists.
889 if package_name[-8:] != '.changes':
890 print "Not a .changes file."
891 print "Please select a .changes file to upload."
892 print "Tried to upload: %s" % package_name
893 sys.exit(1)
894 files_to_upload[:] = []
896 # Construct the package name for further usage.
897 path, name_of_package = os.path.split(package_name)
898 if path == '':
899 path = os.getcwd()
901 # Define the host to upload to.
902 if preferred_host == '':
903 host = guess_upload_host(path, name_of_package)
904 else:
905 host = preferred_host
907 # Check if we already did this upload or not
908 check_upload_logfile(
909 package_name, host, check_only,
910 call_lintian, force_upload, debug)
912 # Run the change file tests.
913 verify_files(
914 path, name_of_package, host, check_only, check_version,
915 unsigned_upload, debug)
917 # Run the lintian test if the user asked us to do so.
918 if (
919 call_lintian or
920 config.getboolean(host, 'run_lintian') == 1):
921 run_lintian_test(os.path.join(path, name_of_package))
922 elif check_only:
923 print "Warning: The option -o does not automatically include "
924 print "a lintian run any more. Please use the option -ol if "
925 print "you want to include running lintian in your checking."
927 # don't upload, skip to the next item
928 if check_only:
929 print "Package checked by dput."
930 continue
932 # Pre-Upload Commands
933 if len(config.get(host, 'pre_upload_command')) != 0:
934 type = 'pre'
935 execute_command(host, debug, type)
937 # Check the upload methods that we have as default and per host
938 if debug:
939 print (
940 "D: Default Method: %s" %
941 config.get('DEFAULT', 'method'))
942 if config.get('DEFAULT', 'method') not in upload_methods:
943 print (
944 "Unknown upload method: %s" %
945 config.get('DEFAULT', 'method'))
946 sys.exit(1)
947 if debug:
948 print "D: Host Method: %s" % config.get(host, 'method')
949 if config.get(host, 'method') not in upload_methods:
950 print "Unknown upload method: %s" % config.get(host, 'method')
951 sys.exit(1)
953 # Inspect the Config and set appropriate upload method
954 if not config.get(host, 'method'):
955 method = config.get('DEFAULT', 'method')
956 else:
957 method = config.get(host, 'method')
959 # Check now the login and redefine it if needed
960 if (
961 len(config.get(host, 'login')) != 0 and
962 config.get(host, 'login') != 'username'):
963 login = config.get(host, 'login')
964 if debug:
965 print "D: Login %s from section %s used" % (login, host)
966 elif (
967 len(config.get('DEFAULT', 'login')) != 0 and
968 config.get('DEFAULT', 'login') != 'username'):
969 login = config.get('DEFAULT', 'login')
970 if debug:
971 print "D: Default login %s used" % login
972 else:
973 if debug:
974 print (
975 "D: Neither host %s nor default login used. Using %s"
976 % (host, login))
978 # Messy, yes. But it isn't referenced by the upload method anyway.
979 if config.get(host, 'method') == 'local':
980 fqdn = 'localhost'
981 else:
982 fqdn = config.get(host, 'fqdn')
983 incoming = config.get(host, 'incoming')
985 # if delay_upload wasn't passed via -e/--delayed
986 if not delay_upload:
987 delay_upload = config.get(host, 'delayed')
988 if not delay_upload:
989 delay_upload = config.get('DEFAULT', 'delayed')
991 if delay_upload:
992 if int(delay_upload) == 0:
993 print "Uploading to DELAYED/0-day."
994 if incoming[-1] == '/':
995 first_char = ''
996 else:
997 first_char = '/'
998 incoming += first_char + 'DELAYED/' + delay_upload + '-day'
999 delayed = ' [DELAYED/' + delay_upload + ']'
1000 else:
1001 delayed = ''
1003 # Do the actual upload
1004 if not simulate:
1005 print (
1006 "Uploading to %s%s (via %s to %s):"
1007 % (host, delayed, method, fqdn))
1008 if debug:
1009 print "D: FQDN: %s" % fqdn
1010 print "D: Login: %s" % login
1011 print "D: Incoming: %s" % incoming
1012 progress = config.getint(host, 'progress_indicator')
1013 if not os.isatty(1):
1014 progress = 0
1015 if method == 'ftp':
1016 if ':' in fqdn:
1017 fqdn, port = fqdn.rsplit(":", 1)
1018 else:
1019 port = 21
1020 ftp_mode = config.getboolean(host, 'passive_ftp')
1021 if ftp_passive_mode == 1:
1022 ftp_mode = 1
1023 if debug:
1024 print "D: FTP port: %s" % port
1025 if ftp_mode == 1:
1026 print "D: Using passive ftp"
1027 else:
1028 print "D: Using active ftp"
1029 upload_methods[method](
1030 fqdn, login, incoming,
1031 files_to_upload, debug, ftp_mode,
1032 progress=progress, port=port)
1033 elif method == 'scp':
1034 if debug and config.getboolean(host, 'scp_compress'):
1035 print "D: Setting compression for scp"
1036 scp_compress = config.getboolean(host, 'scp_compress')
1037 ssh_config_options = [
1038 y for y in (
1039 x.strip() for x in
1040 config.get(host, 'ssh_config_options').split('\n'))
1041 if y]
1042 if debug:
1043 print (
1044 "D: ssh config options:\n "
1045 + '\n '.join(ssh_config_options))
1046 upload_methods[method](
1047 fqdn, login, incoming,
1048 files_to_upload, debug, scp_compress,
1049 ssh_config_options)
1050 else:
1051 upload_methods[method](
1052 fqdn, login, incoming,
1053 files_to_upload, debug, 0, progress=progress)
1054 # Or just simulate it.
1055 else:
1056 for file in files_to_upload:
1057 print 'Uploading with %s: %s to %s:%s' % (
1058 method, file, fqdn, incoming)
1060 # Create the logfile after the package has
1061 # been put into the archive.
1062 if not simulate:
1063 if not no_upload_log:
1064 create_upload_file(
1065 name_of_package, host, path,
1066 files_to_upload, debug)
1067 print "Successfully uploaded packages."
1068 else:
1069 print "Simulated upload."
1071 # Run dinstall if the user asked us to do so.
1072 if debug:
1073 print "D: dinstall: %s" % dinstall
1074 print (
1075 "D: Host Config: %s"
1076 % config.getboolean(host, 'run_dinstall'))
1077 if config.getboolean(host, 'run_dinstall') == 1 or dinstall:
1078 if not simulate:
1079 dinstall_caller(name_of_package, host, login, incoming, debug)
1080 else:
1081 print "Will run dinstall now."
1083 # Post-Upload Command
1084 if len(config.get(host, 'post_upload_command')) != 0:
1085 type = 'post'
1086 execute_command(host, debug, type)
1088 return
1091 if __name__ == '__main__':
1092 try:
1093 main()
1094 except KeyboardInterrupt:
1095 print "Exiting due to user interrupt."
1096 sys.exit(1)
1097 except dputhelper.DputException as e:
1098 sys.stderr.write("%s\n" % e)
1099 sys.exit(1)
1102 # Local variables:
1103 # coding: utf-8
1104 # mode: python
1105 # End:
1106 # vim: fileencoding=utf-8 filetype=python :