2 # -*- coding: utf-8; -*-
5 # Part of ‘dput’, a Debian package upload toolkit.
7 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
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. """
39 from hashlib
import md5
, sha1
43 # Now import our modules
46 app_library_path
= os
.path
.dirname(__file__
)
48 from .helper
import dputhelper
54 def import_upload_functions():
55 """ Import upload method modules and make them available. """
58 package_name
= "methods"
59 modules_path
= os
.path
.join(app_library_path
, package_name
)
61 name
for (__
, name
, ispkg
) in
62 pkgutil
.iter_modules([modules_path
])
65 sys
.stdout
.write("D: modules_found: %r\n" % modules_found
)
66 for module_name
in modules_found
:
67 module
= importlib
.import_module("{package}.{module}".format(
68 package
=".".join(["dput", package_name
]),
71 sys
.stdout
.write("D: Module: %s (%r)\n" % (module_name
, module
))
72 method_name
= module_name
74 sys
.stdout
.write("D: Method name: %s\n" % method_name
)
76 upload_methods
[method_name
] = module
.upload
81 def parse_changes(chg_fd
):
82 """ Parse the changes file. """
83 check
= chg_fd
.read(5)
87 # found a PGP header, gonna ditch the next 3 lines
88 # eat the rest of the line
94 if not chg_fd
.readline().find('Format') != -1:
96 changes_text
= chg_fd
.read()
97 changes
= email
.parser
.HeaderParser().parsestr(changes_text
)
98 if 'files' not in changes
:
99 raise KeyError("No Files field in upload control file")
100 for a
in changes
['files'].strip().split('\n'):
101 if len(a
.split()) != 5:
102 sys
.stderr
.write("Invalid Files line in .changes:\n %s\n" % a
)
107 def read_configs(extra_config
, debug
):
108 """ Read configuration settings from config files.
110 :param extra_config: Filesystem path of config file to read.
111 :param debug: If true, enable debugging output.
112 :return: The resulting `ConfigParser` instance.
114 Read config files in this order:
115 * If specified on the command line, only read `extra_config`.
116 * Otherwise, read ‘/etc/dput.cf’ then ‘~/.dput.cf’.
117 The config parser will layer values.
120 config
= ConfigParser
.ConfigParser()
122 config
.set('DEFAULT', 'login', 'username')
123 config
.set('DEFAULT', 'method', 'scp')
124 config
.set('DEFAULT', 'hash', 'md5')
125 config
.set('DEFAULT', 'allow_unsigned_uploads', '0')
126 config
.set('DEFAULT', 'allow_dcut', '0')
127 config
.set('DEFAULT', 'distributions', '')
128 config
.set('DEFAULT', 'allowed_distributions', '')
129 config
.set('DEFAULT', 'run_lintian', '0')
130 config
.set('DEFAULT', 'run_dinstall', '0')
131 config
.set('DEFAULT', 'check_version', '0')
132 config
.set('DEFAULT', 'scp_compress', '0')
133 config
.set('DEFAULT', 'default_host_main', '')
134 config
.set('DEFAULT', 'post_upload_command', '')
135 config
.set('DEFAULT', 'pre_upload_command', '')
136 config
.set('DEFAULT', 'ssh_config_options', '')
137 config
.set('DEFAULT', 'passive_ftp', '1')
138 config
.set('DEFAULT', 'progress_indicator', '0')
139 config
.set('DEFAULT', 'delayed', '')
142 config_files
= (extra_config
,)
144 config_files
= ('/etc/dput.cf', os
.path
.expanduser("~/.dput.cf"))
146 for config_file
in config_files
:
148 fd
= open(config_file
)
152 "%s: %s, skipping\n" % (e
.strerror
, config_file
))
156 "D: Parsing Configuration File %s\n" % config_file
)
159 except ConfigParser
.ParsingError
as e
:
160 sys
.stderr
.write("Error parsing config file:\n%s\n" % str(e
))
165 "Error: Could not open any configfile, tried %s\n"
166 % (', '.join(config_files
)))
168 # only check for fqdn and incoming dir, rest have reasonable defaults
170 for section
in config
.sections():
171 if config
.get(section
, 'method') == 'local':
172 config
.set(section
, 'fqdn', 'localhost')
174 not config
.has_option(section
, 'fqdn') and
175 config
.get(section
, 'method') != 'local'):
177 "Config error: %s must have a fqdn set\n" % section
)
179 if not config
.has_option(section
, 'incoming'):
181 "Config error: %s must have an incoming directory set\n"
190 hexStr
= string
.hexdigits
193 def hexify_string(string
):
194 """ Convert a string of bytes to hexadecimal text representation. """
196 ord_func
= ord if isinstance(string
, str) else int
198 char
+= hexStr
[(ord_func(c
) >> 4) & 0xF] + hexStr
[ord_func(c
) & 0xF]
202 def checksum_test(filename
, hash_name
):
203 """ Get the hex string for the hash of a file's content.
205 :param filename: Path to the file to read.
206 :param hash_name: Name of the hash to use.
207 :return: The computed hash value, as hexadecimal text.
209 Currently supports md5, sha1. ripemd may come in the future.
213 file_to_test
= open(filename
, 'rb')
215 sys
.stdout
.write("Can't open %s\n" % filename
)
218 if hash_name
== 'md5':
223 check_obj
= hash_type()
226 data
= file_to_test
.read(65536)
229 check_obj
.update(data
)
232 checksum
= hexify_string(check_obj
.digest())
237 def check_signature(filename
):
238 """ Verify the GnuPG signature on a file. """
239 gnupg_path
= "/usr/bin/gpg"
240 if os
.access(filename
, os
.R_OK
):
241 if os
.access(gnupg_path
, os
.X_OK
):
245 "--verify", "--batch", filename
]
246 gnupg_subprocess
= subprocess
.Popen(
247 gnupg_argv
, stdout
=subprocess
.PIPE
)
248 gnupg_stdout
= dputhelper
.make_text_stream(gnupg_subprocess
.stdout
)
249 gnupg_output
= gnupg_stdout
.read()
250 if gnupg_output
.count('[GNUPG:] GOODSIG'):
251 sys
.stdout
.write("Good signature on %s.\n" % filename
)
252 elif gnupg_output
.count('[GNUPG:] BADSIG'):
253 sys
.stdout
.write("Bad signature on %s.\n" % filename
)
255 elif gnupg_output
.count('[GNUPG:] ERRSIG'):
257 "Error verifying signature on %s.\n" % filename
)
259 elif gnupg_output
.count('[GNUPG:] NODATA'):
260 sys
.stdout
.write("No signature on %s.\n" % filename
)
264 "Error in finding signature verification status.\n")
267 "Can't verify signature on %s without GnuPG\n" % filename
)
269 "If you are still using PGP, please read this:\n"
270 "http://www.gnupg.org/gph/en/pgp2x.html\n")
273 sys
.stdout
.write("Can't read %s\n" % filename
)
277 def check_upload_variant(changes
, debug
):
278 """ Check if this is a binary_upload only or not. """
280 if 'architecture' in changes
:
281 arch
= changes
['architecture']
283 sys
.stdout
.write("D: Architecture: %s\n" % arch
)
284 if arch
.find('source') < 0:
286 sys
.stdout
.write("D: Doing a binary upload only.\n")
291 def verify_signature(
292 host
, changes_file
, dsc_file
,
293 config
, check_only
, unsigned_upload
, binary_upload
, debug
):
294 """ Check the signature on the two files given via function call.
296 :param host: Configuration host name.
297 :param changes_file: Filesystem path of upload control file.
298 :param dsc_file: Filesystem path of source control file.
299 :param config: `ConfigParser` instance for this application.
300 :param check_only: If true, no upload is requested.
301 :param unsigned_upload: If true, allow an unsigned upload.
302 :param binary_upload: If true, this upload excludes source.
303 :param debug: If true, enable debugging output.
308 sys
.stdout
.write("D: .changes-File: %s\n" % changes_file
)
309 sys
.stdout
.write("D: .dsc-File: %s\n" % dsc_file
)
310 if ((check_only
or config
.getboolean(host
, 'allow_unsigned_uploads') == 0)
311 and not unsigned_upload
):
312 sys
.stdout
.write("Checking signature on .changes\n")
313 check_signature(changes_file
)
314 if not binary_upload
:
315 sys
.stdout
.write("Checking signature on .dsc\n")
316 check_signature(dsc_file
)
319 def source_check(changes
, debug
):
320 """ Check if a source tarball has to be included in the package or not. """
321 include_orig
= include_tar
= 0
322 if 'version' in changes
:
323 version
= changes
['version']
325 sys
.stdout
.write("D: Package Version: %s\n" % version
)
326 # versions with a dash in them are for non-native only
327 if version
.find('-') == -1:
331 if version
.find(':') > 0:
333 sys
.stdout
.write("D: Epoch found\n")
334 epoch
, version
= version
.split(':', 1)
335 pos
= version
.rfind('-')
336 upstream_version
= version
[0:pos
]
337 debian_version
= version
[pos
+ 1:]
340 "D: Upstream Version: %s\n" % upstream_version
)
341 sys
.stdout
.write("D: Debian Version: %s\n" % debian_version
)
343 debian_version
== '0.1' or debian_version
== '1'
344 or debian_version
== '1.1'):
348 return (include_orig
, include_tar
)
352 path
, filename
, host
,
353 config
, check_only
, check_version
, unsigned_upload
, debug
):
354 """ Run some tests on the files to verify that they are in good shape.
356 :param path: Directory path of the upload control file.
357 :param filename: Filename of the upload control file.
358 :param host: Configuration host name.
359 :param config: `ConfigParser` instance for this application.
360 :param check_only: If true, no upload is requested.
361 :param check_version: If true, check the package version
363 :param unsigned_upload: If true, allow an unsigned upload.
364 :param debug: If true, enable debugging output.
365 :return: A collection of filesystem paths of all files to upload.
368 file_seen
= include_orig_tar_gz
= include_tar_gz
= binary_only
= 0
371 name_of_file
= filename
373 change_file
= os
.path
.join(path
, name_of_file
)
377 "D: Validating contents of changes file %s\n" % change_file
)
379 chg_fd
= open(change_file
, 'r')
381 sys
.stdout
.write("Can't open %s\n" % change_file
)
383 changes
= parse_changes(chg_fd
)
386 # Find out if it's a binary only upload or not
387 binary_upload
= check_upload_variant(changes
, debug
)
393 for file in changes
['files'].strip().split('\n'):
395 filename
= file.split()[4]
396 if filename
.find('.dsc') != -1:
398 sys
.stdout
.write("D: dsc-File: %s\n" % filename
)
399 dsc_file
= os
.path
.join(path
, filename
)
401 sys
.stderr
.write("Error: no dsc file found in sourceful upload\n")
404 # Run the check to verify that the package has been tested.
406 if config
.getboolean(host
, 'check_version') == 1 or check_version
:
407 version_check(path
, changes
, debug
)
408 except ConfigParser
.NoSectionError
as e
:
409 sys
.stderr
.write("Error in config file:\n%s\n" % str(e
))
412 # Verify the signature of the maintainer
414 host
, change_file
, dsc_file
,
415 config
, check_only
, unsigned_upload
, binary_upload
, debug
)
418 (include_orig_tar_gz
, include_tar_gz
) = source_check(changes
, debug
)
420 # Check md5sum and the size
421 file_list
= changes
['files'].strip().split('\n')
422 hash_name
= config
.get('DEFAULT', 'hash')
423 for line
in file_list
:
424 (check_sum
, size
, section
, priority
, file) = line
.split()
425 file_to_upload
= os
.path
.join(path
, file)
427 sys
.stdout
.write("D: File to upload: %s\n" % file_to_upload
)
428 if checksum_test(file_to_upload
, hash_name
) != check_sum
:
431 "D: Checksum from .changes: %s\n" % check_sum
)
433 "D: Generated Checksum: %s\n" %
434 checksum_test(file_to_upload
, hash_name
))
436 "Checksum doesn't match for %s\n" % file_to_upload
)
441 "D: Checksum for %s is fine\n" % file_to_upload
)
442 if os
.stat(file_to_upload
)[stat
.ST_SIZE
] != int(size
):
444 sys
.stdout
.write("D: size from .changes: %s\n" % size
)
446 "D: calculated size: %s\n"
447 % os
.stat(file_to_upload
)[stat
.ST_SIZE
])
449 "size doesn't match for %s\n" % file_to_upload
)
451 files_to_upload
.append(file_to_upload
)
454 for file in files_to_upload
:
455 if file[-12:] == '.orig.tar.gz' and not include_orig_tar_gz
:
457 sys
.stdout
.write("D: Filename: %s\n" % file)
458 sys
.stdout
.write("D: Suffix: %s\n\n" % file[-12:])
460 "Package includes an .orig.tar.gz file although"
461 " the debian revision suggests\n"
462 "that it might not be required."
463 " Multiple uploads of the .orig.tar.gz may be\n"
464 "rejected by the upload queue management software.\n")
466 file[-7:] == '.tar.gz' and not include_tar_gz
467 and not include_orig_tar_gz
):
469 sys
.stdout
.write("D: Filename: %s\n" % file)
470 sys
.stdout
.write("D: Suffix: %s\n" % file[-7:])
472 "Package includes a .tar.gz file although"
473 " the version suggests that it might\n"
475 " Multiple uploads of the .tar.gz may be rejected by the\n"
476 "upload queue management software.\n")
478 distribution
= changes
.get('distribution')
479 allowed_distributions
= config
.get(host
, 'allowed_distributions')
480 if distribution
and allowed_distributions
:
483 "D: Checking: distribution %s matches %s\n"
484 % (distribution
, allowed_distributions
))
485 if not re
.match(allowed_distributions
, distribution
):
486 raise dputhelper
.DputUploadFatalException(
487 "Error: uploading files for distribution %s to %s"
489 % (distribution
, host
))
492 sys
.stdout
.write("D: File to upload: %s\n" % change_file
)
493 files_to_upload
.append(change_file
)
495 return files_to_upload
498 def print_config(config
, debug
):
499 """ Print the configuration and exit. """
500 sys
.stdout
.write("\n")
501 config
.write(sys
.stdout
)
502 sys
.stdout
.write("\n")
505 def create_upload_file(package
, host
, fqdn
, path
, files_to_upload
, debug
):
506 """ Write the log file for the upload.
508 :param package: File name of package to upload.
509 :param host: Configuration host name.
510 :param fqdn: Fully-qualified domain name of the remote host.
511 :param path: Filesystem path of the upload control file.
512 :param debug: If true, enable debugging output.
515 The upload log file is named ‘basename.hostname.upload’, where
516 “basename” is the package file name without suffix, and
517 “hostname” is the name of the host as specified in the
520 For example, uploading ‘foo_1.2.3-1_xyz.deb’ to host ‘bar’
521 will be logged to ‘foo_1.2.3-1_xyz.bar.upload’.
523 The upload log file is written to the
524 directory containing the upload control file.
527 # only need first part
528 base
= os
.path
.splitext(package
)[0]
529 logfile_name
= os
.path
.join(path
, base
+ '.' + host
+ '.upload')
531 sys
.stdout
.write("D: Writing logfile: %s\n" % logfile_name
)
533 if os
.access(logfile_name
, os
.R_OK
):
534 logfile_fd
= open(logfile_name
, 'a')
536 logfile_fd
= open(logfile_name
, 'w')
538 sys
.stderr
.write("Could not write %s\n" % logfile_name
)
541 for file in files_to_upload
:
542 entry_for_logfile
= (
543 'Successfully uploaded ' + os
.path
.basename(file) +
544 ' to ' + fqdn
+ ' for ' + host
+ '.\n')
545 logfile_fd
.write(entry_for_logfile
)
549 def run_lintian_test(changes_file
):
550 """ Run lintian on the changes file and stop if it finds errors. """
552 if os
.access(changes_file
, os
.R_OK
):
553 if os
.access("/usr/bin/lintian", os
.R_OK
):
554 old_signal
= signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
)
555 sys
.stdout
.write("Package is now being checked with lintian.\n")
556 if dputhelper
.check_call(
557 ['lintian', '-i', changes_file
]
558 ) != dputhelper
.EXIT_STATUS_SUCCESS
:
561 "Lintian says this package is not compliant"
562 " with the current policy.\n"
563 "Please check the current policy and your package.\n"
564 "Also see lintian documentation about overrides.\n")
567 signal
.signal(signal
.SIGPIPE
, old_signal
)
571 "lintian is not installed, skipping package test.\n")
573 sys
.stdout
.write("Can't read %s\n" % changes_file
)
577 def guess_upload_host(path
, filename
, config
):
578 """ Guess the host where the package should be uploaded to.
580 :param path: Directory path of the upload control file.
581 :param filename: Filename of the upload control file.
582 :param config: `ConfigParser` instance for this application.
583 :return: The hostname determined for this upload.
585 This is based on information from the upload control
591 dist_re
= re
.compile(r
'^Distribution: (.*)')
593 name_of_file
= filename
594 changes_file
= os
.path
.join(path
, name_of_file
)
597 changes_file_fd
= open(changes_file
, 'r')
599 sys
.stdout
.write("Can't open %s\n" % changes_file
)
601 lines
= changes_file_fd
.readlines()
603 match
= dist_re
.search(line
)
605 distribution
= match
.group(1)
607 # Try to guess a host based on the Distribution: field
609 for section
in config
.sections():
610 host_dists
= config
.get(section
, 'distributions')
613 for host_dist
in host_dists
.split(','):
614 if distribution
== host_dist
.strip():
617 "D: guessing host %s"
618 " based on distribution %s\n"
619 % (section
, host_dist
))
622 if len(config
.get('DEFAULT', 'default_host_main')) != 0:
624 "Trying to upload package to %s\n"
625 % config
.get('DEFAULT', 'default_host_main'))
626 return config
.get('DEFAULT', 'default_host_main')
629 "Trying to upload package to ftp-master"
630 " (ftp.upload.debian.org)\n")
634 def dinstall_caller(filename
, host
, fqdn
, login
, incoming
, debug
):
635 """ Run ‘dinstall’ for the package on the remote host.
637 :param filename: Debian package filename to install.
638 :param host: Configuration host name.
639 :param fqdn: Fully-qualified domain name of the remote host.
640 :param login: Username for login to the remote host.
641 :param incoming: Filesystem path on remote host for incoming
643 :param debug: If true, enable debugging output.
646 Run ‘dinstall’ on the remote host in test mode, and present
647 the output to the user.
649 This is so the user can see if the package would be installed
654 'ssh', '%s@%s' % (login
, fqdn
),
655 'cd', '%s' % incoming
,
656 ';', 'dinstall', '-n', '%s' % filename
]
659 "D: Logging into %s@%s:%s\n" % (login
, host
, incoming
))
660 sys
.stdout
.write("D: dinstall -n %s\n" % filename
)
661 if dputhelper
.check_call(command
) != dputhelper
.EXIT_STATUS_SUCCESS
:
663 "Error occured while trying to connect, or while"
664 " attempting to run dinstall.\n")
668 def version_check(path
, changes
, debug
):
669 """ Check if the caller has installed the package also on his system.
671 This is for testing purposes before uploading it. If not, we
678 dpkg_proc
= subprocess
.Popen(
679 'dpkg --print-architecture',
680 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
681 shell
=True, close_fds
=True)
682 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
683 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
684 dpkg_output
= dpkg_stdout
.read()
685 dpkg_architecture
= dpkg_output
.strip()
687 dpkg_stderr_output
= dpkg_stderr
.read()
689 if debug
and dpkg_stderr_output
:
691 "D: dpkg-architecture stderr output:"
692 " %r\n" % dpkg_stderr_output
)
695 "D: detected architecture: '%s'\n" % dpkg_architecture
)
697 # Get filenames of deb files:
698 for file in changes
['files'].strip().split('\n'):
699 filename
= os
.path
.join(path
, file.split()[4])
700 if filename
.endswith('.deb'):
702 sys
.stdout
.write("D: Debian Package: %s\n" % filename
)
703 dpkg_proc
= subprocess
.Popen(
704 'dpkg --field %s' % filename
,
705 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
706 shell
=True, close_fds
=True)
707 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
708 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
709 dpkg_output
= dpkg_stdout
.read()
711 dpkg_fields
= email
.parser
.HeaderParser().parsestr(dpkg_output
)
712 dpkg_stderr_output
= dpkg_stderr
.read()
714 if debug
and dpkg_stderr_output
:
716 "D: dpkg stderr output:"
717 " %r\n" % dpkg_stderr_output
)
720 and dpkg_fields
['architecture'] not in [
721 'all', dpkg_architecture
]):
724 "D: not install-checking %s due to arch mismatch\n"
727 package_name
= dpkg_fields
['package']
728 version_number
= dpkg_fields
['version']
731 "D: Package to Check: %s\n" % package_name
)
734 "D: Version to Check: %s\n" % version_number
)
735 files_to_check
.append((package_name
, version_number
))
737 for file, version_to_check
in files_to_check
:
739 sys
.stdout
.write("D: Name of Package: %s\n" % file)
740 dpkg_proc
= subprocess
.Popen(
742 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
743 shell
=True, close_fds
=True)
744 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
745 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
746 dpkg_output
= dpkg_stdout
.read()
748 dpkg_fields
= email
.parser
.HeaderParser().parsestr(dpkg_output
)
749 dpkg_stderr_output
= dpkg_stderr
.read()
751 if debug
and dpkg_stderr_output
:
753 "D: dpkg stderr output:"
754 " %r\n" % dpkg_stderr_output
)
755 if 'version' in dpkg_fields
:
756 installed_version
= dpkg_fields
['version']
759 "D: Installed-Version: %s\n" % installed_version
)
762 "D: Check-Version: %s\n" % version_to_check
)
763 if installed_version
!= version_to_check
:
765 "Package to upload is not installed, but it appears"
766 " you have an older version installed.\n")
769 "Uninstalled Package. Test it before uploading it.\n")
773 def execute_command(command
, position
, debug
=False):
774 """ Run a command that the user-defined in the config_file.
776 :param command: Command line to execute.
777 :param position: Position of the command: 'pre' or 'post'.
778 :param debug: If true, enable debugging output.
783 sys
.stdout
.write("D: Command: %s\n" % command
)
784 if subprocess
.call(command
, shell
=True):
785 raise dputhelper
.DputUploadFatalException(
786 "Error: %s upload command failed." % position
)
789 def check_upload_logfile(
790 changes_file
, host
, fqdn
,
791 check_only
, call_lintian
, force_upload
, debug
):
792 """ Check if the user already put this package on the specified host.
794 :param changes_file: Filesystem path of upload control file.
795 :param host: Configuration host name.
796 :param fqdn: Fully-qualified domain name of the remote host.
797 :param check_only: If true, no upload is requested.
798 :param call_lintian: If true, a Lintian invocation is requested.
799 :param force_upload: If true, don't check the upload log file.
800 :param debug: If true, enable debugging output.
805 upload_logfile
= changes_file
[:-8] + '.' + host
+ '.upload'
806 if not check_only
and not force_upload
:
807 if not os
.path
.exists(upload_logfile
):
810 fd_logfile
= open(upload_logfile
)
812 sys
.stdout
.write("Couldn't open %s\n" % upload_logfile
)
814 for line
in fd_logfile
.readlines():
815 if line
.find(fqdn
) != -1:
819 "Package has already been uploaded to %s on %s\n"
821 sys
.stdout
.write("Nothing more to do for %s\n" % changes_file
)
825 def make_usage_message():
826 """ Make the program usage help message. """
827 text
= textwrap
.dedent("""\
828 Usage: dput [options] [host] <package(s).changes>
829 Supported options (see man page for long forms):
830 -c: Config file to parse.
831 -d: Enable debug messages.
832 -D: Run dinstall after upload.
833 -e: Upload to a delayed queue. Takes an argument from 0 to 15.
835 -h: Display this help message.
836 -H: Display a list of hosts from the config file.
837 -l: Run lintian before upload.
838 -U: Do not write a .upload file after uploading.
839 -o: Only check the package.
840 -p: Print the configuration.
841 -P: Use passive mode for ftp uploads.
842 -s: Simulate the upload only.
843 -u: Don't check GnuPG signature.
844 -v: Display version information.
845 -V: Check the package version and then upload it.
851 """ Main function, no further comment needed. :) """
855 check_version
= config_print
= force_upload
= 0
856 call_lintian
= no_upload_log
= config_host_list
= 0
862 unsigned_upload
= False
866 progname
= dputhelper
.get_progname()
867 version
= dputhelper
.get_distribution_version()
869 # Parse Command Line Options.
870 (opts
, args
) = dputhelper
.getopt(
872 'c:dDe:fhHlUopPsuvV', [
873 'debug', 'dinstall', 'check-only',
874 'check-version', 'config=', 'force', 'help',
875 'host-list', 'lintian', 'no-upload-log',
876 'passive', 'print', 'simulate', 'unchecked',
877 'delayed=', 'version'])
878 for option
, arg
in opts
:
879 if option
in ('-h', '--help'):
880 sys
.stdout
.write(make_usage_message())
882 elif option
in ('-v', '--version'):
883 sys
.stdout
.write("{progname} {version}\n".format(
884 progname
=progname
, version
=version
))
886 elif option
in ('-d', '--debug'):
888 elif option
in ('-D', '--dinstall'):
890 elif option
in ('-c', '--config'):
892 elif option
in ('-f', '--force'):
894 elif option
in ('-H', '--host-list'):
896 elif option
in ('-l', '--lintian'):
898 elif option
in ('-U', '--no-upload-log'):
900 elif option
in ('-o', '--check-only'):
902 elif option
in ('-p', '--print'):
904 elif option
in ('-P', '--passive'):
906 elif option
in ('-s', '--simulate'):
908 elif option
in ('-u', '--unchecked'):
909 unsigned_upload
= True
910 elif option
in ('-e', '--delayed'):
911 if arg
in map(str, range(16)):
915 "Incorrect delayed argument,"
916 " dput only understands 0 to 15.\n")
918 elif option
in ('-V', '--check_version'):
921 # Always print the version number in the debug output
922 # so that in case of bugreports, we know which version
923 # the user has installed
926 "D: {progname} {version}\n".format(
927 progname
=progname
, version
=version
))
929 # Try to get the login from the enviroment
930 if 'USER' in os
.environ
:
931 login
= os
.environ
['USER']
933 sys
.stdout
.write("D: Login: %s\n" % login
)
935 sys
.stdout
.write("$USER not set, will use login information.\n")
936 # Else use the current username
937 login
= pwd
.getpwuid(os
.getuid())[0]
939 sys
.stdout
.write("D: User-ID: %s\n" % os
.getuid())
940 sys
.stdout
.write("D: Login: %s\n" % login
)
942 # Start Config File Parsing.
943 config
= read_configs(config_file
, debug
)
946 print_config(config
, debug
)
952 "Default Method: %s\n"
953 "\n" % config
.get('DEFAULT', 'method'))
954 for section
in config
.sections():
956 if config
.get(section
, 'distributions'):
958 ", distributions: %s" %
959 config
.get(section
, 'distributions'))
961 "%s => %s (Upload method: %s%s)\n" % (
963 config
.get(section
, 'fqdn'),
964 config
.get(section
, 'method'),
966 sys
.stdout
.write("\n")
969 # Process further command line options.
972 "No package or host has been provided, see dput -h\n")
974 elif len(args
) == 1 and not check_only
:
975 package_to_upload
= args
[0:]
980 "D: Checking if a host was named"
981 " on the command line.\n")
982 if config
.has_section(args
[0]):
984 sys
.stdout
.write("D: Host %s found in config\n" % args
[0])
985 # Host was also named, so only the rest will be a list
986 # of packages to upload.
987 preferred_host
= args
[0]
988 package_to_upload
= args
[1:]
990 not config
.has_section(args
[0])
991 and not args
[0].endswith('.changes')):
992 sys
.stderr
.write("No host %s found in config\n" % args
[0])
993 if args
[0] == 'gluck_delayed':
995 The delayed upload queue has been moved back to
996 ftp-master (aka ftp.upload.debian.org).
1001 sys
.stdout
.write("D: No host named on command line.\n")
1002 # Only packages have been named on the command line.
1004 package_to_upload
= args
[0:]
1007 sys
.stdout
.write("D: Checking for the package name.\n")
1008 if config
.has_section(args
[0]):
1009 sys
.stdout
.write("D: Host %s found in config.\n" % args
[0])
1010 preferred_host
= args
[0]
1011 package_to_upload
= args
[1:]
1012 elif not config
.has_section(args
[0]):
1013 sys
.stdout
.write("D: No host %s found in config\n" % args
[0])
1014 package_to_upload
= args
[0:]
1016 upload_methods
= import_upload_functions()
1018 # Run the same checks for all packages that have been given on
1020 for package_name
in package_to_upload
:
1021 # Check that a .changes file was given on the command line
1022 # and no matching .upload file exists.
1023 if package_name
[-8:] != '.changes':
1025 "Not a .changes file.\n"
1026 "Please select a .changes file to upload.\n")
1027 sys
.stdout
.write("Tried to upload: %s\n" % package_name
)
1030 # Construct the package name for further usage.
1031 path
, name_of_package
= os
.path
.split(package_name
)
1035 # Define the host to upload to.
1036 if preferred_host
== '':
1037 host
= guess_upload_host(path
, name_of_package
, config
)
1039 host
= preferred_host
1040 if config
.get(host
, 'method') == 'local':
1043 fqdn
= config
.get(host
, 'fqdn')
1045 # Check if we already did this upload or not
1046 check_upload_logfile(
1047 package_name
, host
, fqdn
,
1048 check_only
, call_lintian
, force_upload
, debug
)
1050 # Run the change file tests.
1051 files_to_upload
= verify_files(
1052 path
, name_of_package
, host
,
1053 config
, check_only
, check_version
, unsigned_upload
, debug
)
1055 # Run the lintian test if the user asked us to do so.
1058 config
.getboolean(host
, 'run_lintian') == 1):
1059 run_lintian_test(os
.path
.join(path
, name_of_package
))
1062 "Warning: The option -o does not automatically include \n"
1063 "a lintian run any more. Please use the option -ol if \n"
1064 "you want to include running lintian in your checking.\n")
1066 # don't upload, skip to the next item
1068 sys
.stdout
.write("Package checked by dput.\n")
1071 # Pre-Upload Commands
1072 if len(config
.get(host
, 'pre_upload_command')) != 0:
1074 command
= config
.get(host
, 'pre_upload_command')
1075 execute_command(command
, position
, debug
)
1077 # Check the upload methods that we have as default and per host
1080 "D: Default Method: %s\n"
1081 % config
.get('DEFAULT', 'method'))
1082 if config
.get('DEFAULT', 'method') not in upload_methods
:
1084 "Unknown upload method: %s\n"
1085 % config
.get('DEFAULT', 'method'))
1089 "D: Host Method: %s\n" % config
.get(host
, 'method'))
1090 if config
.get(host
, 'method') not in upload_methods
:
1092 "Unknown upload method: %s\n"
1093 % config
.get(host
, 'method'))
1096 # Inspect the Config and set appropriate upload method
1097 if not config
.get(host
, 'method'):
1098 method
= config
.get('DEFAULT', 'method')
1100 method
= config
.get(host
, 'method')
1102 # Check now the login and redefine it if needed
1104 len(config
.get(host
, 'login')) != 0 and
1105 config
.get(host
, 'login') != 'username'):
1106 login
= config
.get(host
, 'login')
1109 "D: Login %s from section %s used\n" % (login
, host
))
1111 len(config
.get('DEFAULT', 'login')) != 0 and
1112 config
.get('DEFAULT', 'login') != 'username'):
1113 login
= config
.get('DEFAULT', 'login')
1115 sys
.stdout
.write("D: Default login %s used\n" % login
)
1119 "D: Neither host %s nor default login used. Using %s\n"
1122 incoming
= config
.get(host
, 'incoming')
1124 # if delay_upload wasn't passed via -e/--delayed
1125 if delay_upload
is None:
1126 delay_upload
= config
.get(host
, 'delayed')
1127 if not delay_upload
:
1128 delay_upload
= config
.get('DEFAULT', 'delayed')
1131 if int(delay_upload
) == 0:
1132 sys
.stdout
.write("Uploading to DELAYED/0-day.\n")
1133 if incoming
[-1] == '/':
1137 incoming
+= first_char
+ 'DELAYED/' + delay_upload
+ '-day'
1138 delayed
= ' [DELAYED/' + delay_upload
+ ']'
1142 # Do the actual upload
1145 "Uploading to %s%s (via %s to %s):\n"
1146 % (host
, delayed
, method
, fqdn
))
1148 sys
.stdout
.write("D: FQDN: %s\n" % fqdn
)
1149 sys
.stdout
.write("D: Login: %s\n" % login
)
1150 sys
.stdout
.write("D: Incoming: %s\n" % incoming
)
1151 progress
= config
.getint(host
, 'progress_indicator')
1152 if not os
.isatty(1):
1156 fqdn
, port
= fqdn
.rsplit(":", 1)
1159 ftp_mode
= config
.getboolean(host
, 'passive_ftp')
1160 if ftp_passive_mode
== 1:
1163 sys
.stdout
.write("D: FTP port: %s\n" % port
)
1165 sys
.stdout
.write("D: Using passive ftp\n")
1167 sys
.stdout
.write("D: Using active ftp\n")
1168 upload_methods
[method
](
1169 fqdn
, login
, incoming
,
1170 files_to_upload
, debug
, ftp_mode
,
1171 progress
=progress
, port
=port
)
1172 elif method
== 'scp':
1173 if debug
and config
.getboolean(host
, 'scp_compress'):
1174 sys
.stdout
.write("D: Setting compression for scp\n")
1175 scp_compress
= config
.getboolean(host
, 'scp_compress')
1176 ssh_config_options
= [
1179 config
.get(host
, 'ssh_config_options').split('\n'))
1183 "D: ssh config options:"
1185 + "\n ".join(ssh_config_options
)
1187 upload_methods
[method
](
1188 fqdn
, login
, incoming
,
1189 files_to_upload
, debug
, scp_compress
,
1192 upload_methods
[method
](
1193 fqdn
, login
, incoming
,
1194 files_to_upload
, debug
, 0, progress
=progress
)
1195 # Or just simulate it.
1197 for file in files_to_upload
:
1199 "Uploading with %s: %s to %s:%s\n"
1200 % (method
, file, fqdn
, incoming
))
1202 # Create the logfile after the package has
1203 # been put into the archive.
1205 if not no_upload_log
:
1207 name_of_package
, host
, fqdn
, path
,
1208 files_to_upload
, debug
)
1209 sys
.stdout
.write("Successfully uploaded packages.\n")
1211 sys
.stdout
.write("Simulated upload.\n")
1213 # Run dinstall if the user asked us to do so.
1215 sys
.stdout
.write("D: dinstall: %s\n" % dinstall
)
1217 "D: Host Config: %s\n"
1218 % config
.getboolean(host
, 'run_dinstall'))
1219 if config
.getboolean(host
, 'run_dinstall') == 1 or dinstall
:
1222 name_of_package
, host
, fqdn
, login
, incoming
, debug
)
1224 sys
.stdout
.write("Will run dinstall now.\n")
1226 # Post-Upload Command
1227 if len(config
.get(host
, 'post_upload_command')) != 0:
1229 command
= config
.get(host
, 'post_upload_command')
1230 execute_command(command
, position
, debug
)
1235 if __name__
== '__main__':
1238 except KeyboardInterrupt:
1239 sys
.stdout
.write("Exiting due to user interrupt.\n")
1241 except dputhelper
.DputException
as e
:
1242 sys
.stderr
.write("%s\n" % e
)
1250 # vim: fileencoding=utf-8 filetype=python :