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__
)
49 from .helper
import dputhelper
55 def import_upload_functions():
56 """ Import upload method modules and make them available. """
59 package_name
= "methods"
60 modules_path
= os
.path
.join(app_library_path
, package_name
)
62 name
for (__
, name
, ispkg
) in
63 pkgutil
.iter_modules([modules_path
])
66 sys
.stdout
.write("D: modules_found: %r\n" % modules_found
)
67 for module_name
in modules_found
:
68 module
= importlib
.import_module("{package}.{module}".format(
69 package
=".".join(["dput", package_name
]),
72 sys
.stdout
.write("D: Module: %s (%r)\n" % (module_name
, module
))
73 method_name
= module_name
75 sys
.stdout
.write("D: Method name: %s\n" % method_name
)
77 upload_methods
[method_name
] = module
.upload
82 def parse_changes(chg_fd
):
83 """ Parse the changes file. """
84 check
= chg_fd
.read(5)
88 # found a PGP header, gonna ditch the next 3 lines
89 # eat the rest of the line
95 if not chg_fd
.readline().find('Format') != -1:
97 changes_text
= chg_fd
.read()
98 changes
= email
.parser
.HeaderParser().parsestr(changes_text
)
99 if 'files' not in changes
:
100 raise KeyError("No Files field in upload control file")
101 for a
in changes
['files'].strip().split('\n'):
102 if len(a
.split()) != 5:
103 sys
.stderr
.write("Invalid Files line in .changes:\n %s\n" % a
)
108 def read_configs(extra_config
, debug
):
109 """ Read configuration settings from config files.
111 :param extra_config: Filesystem path of config file to read.
112 :param debug: If true, enable debugging output.
113 :return: The resulting `ConfigParser` instance.
115 Read config files 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 config
= ConfigParser
.ConfigParser()
123 config
.set('DEFAULT', 'login', 'username')
124 config
.set('DEFAULT', 'method', 'scp')
125 config
.set('DEFAULT', 'hash', 'md5')
126 config
.set('DEFAULT', 'allow_unsigned_uploads', '0')
127 config
.set('DEFAULT', 'allow_dcut', '0')
128 config
.set('DEFAULT', 'distributions', '')
129 config
.set('DEFAULT', 'allowed_distributions', '')
130 config
.set('DEFAULT', 'run_lintian', '0')
131 config
.set('DEFAULT', 'run_dinstall', '0')
132 config
.set('DEFAULT', 'check_version', '0')
133 config
.set('DEFAULT', 'scp_compress', '0')
134 config
.set('DEFAULT', 'default_host_main', '')
135 config
.set('DEFAULT', 'post_upload_command', '')
136 config
.set('DEFAULT', 'pre_upload_command', '')
137 config
.set('DEFAULT', 'ssh_config_options', '')
138 config
.set('DEFAULT', 'passive_ftp', '1')
139 config
.set('DEFAULT', 'progress_indicator', '0')
140 config
.set('DEFAULT', 'delayed', '')
143 config_files
= (extra_config
,)
145 config_files
= ('/etc/dput.cf', os
.path
.expanduser("~/.dput.cf"))
147 for config_file
in config_files
:
149 fd
= open(config_file
)
153 "%s: %s, skipping\n" % (e
.strerror
, config_file
))
157 "D: Parsing Configuration File %s\n" % config_file
)
160 except ConfigParser
.ParsingError
as e
:
161 sys
.stderr
.write("Error parsing config file:\n%s\n" % str(e
))
166 "Error: Could not open any configfile, tried %s\n"
167 % (', '.join(config_files
)))
169 # only check for fqdn and incoming dir, rest have reasonable defaults
171 for section
in config
.sections():
172 if config
.get(section
, 'method') == 'local':
173 config
.set(section
, 'fqdn', 'localhost')
175 not config
.has_option(section
, 'fqdn') and
176 config
.get(section
, 'method') != 'local'):
178 "Config error: %s must have a fqdn set\n" % section
)
180 if not config
.has_option(section
, 'incoming'):
182 "Config error: %s must have an incoming directory set\n"
191 hexStr
= string
.hexdigits
194 def hexify_string(string
):
195 """ Convert a string of bytes to hexadecimal text representation. """
197 ord_func
= ord if isinstance(string
, str) else int
199 char
+= hexStr
[(ord_func(c
) >> 4) & 0xF] + hexStr
[ord_func(c
) & 0xF]
203 def checksum_test(filename
, hash_name
):
204 """ Get the hex string for the hash of a file's content.
206 :param filename: Path to the file to read.
207 :param hash_name: Name of the hash to use.
208 :return: The computed hash value, as hexadecimal text.
210 Currently supports md5, sha1. ripemd may come in the future.
214 file_to_test
= open(filename
, 'rb')
216 sys
.stdout
.write("Can't open %s\n" % filename
)
219 if hash_name
== 'md5':
224 check_obj
= hash_type()
227 data
= file_to_test
.read(65536)
230 check_obj
.update(data
)
233 checksum
= hexify_string(check_obj
.digest())
238 def check_upload_variant(changes
, debug
):
239 """ Check if this is a binary_upload only or not. """
241 if 'architecture' in changes
:
242 arch
= changes
['architecture']
244 sys
.stdout
.write("D: Architecture: %s\n" % arch
)
245 if arch
.find('source') < 0:
247 sys
.stdout
.write("D: Doing a binary upload only.\n")
252 def verify_signature(
253 host
, changes_file_path
, dsc_file_path
,
254 config
, check_only
, unsigned_upload
, binary_upload
, debug
):
255 """ Check the signature on the two files given via function call.
257 :param host: Configuration host name.
258 :param changes_file_path: Filesystem path of upload control file.
259 :param dsc_file_path: Filesystem path of source control file.
260 :param config: `ConfigParser` instance for this application.
261 :param check_only: If true, no upload is requested.
262 :param unsigned_upload: If true, allow an unsigned upload.
263 :param binary_upload: If true, this upload excludes source.
264 :param debug: If true, enable debugging output.
269 def assert_good_signature_or_exit(path
):
270 """ Assert the signature on the file at `path` is good. """
272 with
open(path
) as infile
:
273 crypto
.check_file_signature(infile
)
274 except Exception as exc
:
275 if isinstance(exc
, crypto
.gpgme
.GpgmeError
):
276 sys
.stdout
.write("{}\n".format(exc
))
283 "D: upload control file: {}\n".format(changes_file_path
))
285 "D: source control file: {}\n".format(dsc_file_path
))
286 if ((check_only
or config
.getboolean(host
, 'allow_unsigned_uploads') == 0)
287 and not unsigned_upload
):
288 sys
.stdout
.write("Checking signature on .changes\n")
289 assert_good_signature_or_exit(changes_file_path
)
290 if not binary_upload
:
291 sys
.stdout
.write("Checking signature on .dsc\n")
292 assert_good_signature_or_exit(dsc_file_path
)
295 def source_check(changes
, debug
):
296 """ Check if a source tarball has to be included in the package or not. """
297 include_orig
= include_tar
= 0
298 if 'version' in changes
:
299 version
= changes
['version']
301 sys
.stdout
.write("D: Package Version: %s\n" % version
)
302 # versions with a dash in them are for non-native only
303 if version
.find('-') == -1:
307 if version
.find(':') > 0:
309 sys
.stdout
.write("D: Epoch found\n")
310 epoch
, version
= version
.split(':', 1)
311 pos
= version
.rfind('-')
312 upstream_version
= version
[0:pos
]
313 debian_version
= version
[pos
+ 1:]
316 "D: Upstream Version: %s\n" % upstream_version
)
317 sys
.stdout
.write("D: Debian Version: %s\n" % debian_version
)
319 debian_version
== '0.1' or debian_version
== '1'
320 or debian_version
== '1.1'):
324 return (include_orig
, include_tar
)
328 path
, filename
, host
,
329 config
, check_only
, check_version
, unsigned_upload
, debug
):
330 """ Run some tests on the files to verify that they are in good shape.
332 :param path: Directory path of the upload control file.
333 :param filename: Filename of the upload control file.
334 :param host: Configuration host name.
335 :param config: `ConfigParser` instance for this application.
336 :param check_only: If true, no upload is requested.
337 :param check_version: If true, check the package version
339 :param unsigned_upload: If true, allow an unsigned upload.
340 :param debug: If true, enable debugging output.
341 :return: A collection of filesystem paths of all files to upload.
344 file_seen
= include_orig_tar_gz
= include_tar_gz
= binary_only
= 0
347 name_of_file
= filename
349 change_file
= os
.path
.join(path
, name_of_file
)
353 "D: Validating contents of changes file %s\n" % change_file
)
355 chg_fd
= open(change_file
, 'r')
357 sys
.stdout
.write("Can't open %s\n" % change_file
)
359 changes
= parse_changes(chg_fd
)
362 # Find out if it's a binary only upload or not
363 binary_upload
= check_upload_variant(changes
, debug
)
369 for file in changes
['files'].strip().split('\n'):
371 filename
= file.split()[4]
372 if filename
.find('.dsc') != -1:
374 sys
.stdout
.write("D: dsc-File: %s\n" % filename
)
375 dsc_file
= os
.path
.join(path
, filename
)
377 sys
.stderr
.write("Error: no dsc file found in sourceful upload\n")
380 # Run the check to verify that the package has been tested.
382 if config
.getboolean(host
, 'check_version') == 1 or check_version
:
383 version_check(path
, changes
, debug
)
384 except ConfigParser
.NoSectionError
as e
:
385 sys
.stderr
.write("Error in config file:\n%s\n" % str(e
))
388 # Verify the signature of the maintainer
390 host
, change_file
, dsc_file
,
391 config
, check_only
, unsigned_upload
, binary_upload
, debug
)
394 (include_orig_tar_gz
, include_tar_gz
) = source_check(changes
, debug
)
396 # Check md5sum and the size
397 file_list
= changes
['files'].strip().split('\n')
398 hash_name
= config
.get('DEFAULT', 'hash')
399 for line
in file_list
:
400 (check_sum
, size
, section
, priority
, file) = line
.split()
401 file_to_upload
= os
.path
.join(path
, file)
403 sys
.stdout
.write("D: File to upload: %s\n" % file_to_upload
)
404 if checksum_test(file_to_upload
, hash_name
) != check_sum
:
407 "D: Checksum from .changes: %s\n" % check_sum
)
409 "D: Generated Checksum: %s\n" %
410 checksum_test(file_to_upload
, hash_name
))
412 "Checksum doesn't match for %s\n" % file_to_upload
)
417 "D: Checksum for %s is fine\n" % file_to_upload
)
418 if os
.stat(file_to_upload
)[stat
.ST_SIZE
] != int(size
):
420 sys
.stdout
.write("D: size from .changes: %s\n" % size
)
422 "D: calculated size: %s\n"
423 % os
.stat(file_to_upload
)[stat
.ST_SIZE
])
425 "size doesn't match for %s\n" % file_to_upload
)
427 files_to_upload
.append(file_to_upload
)
430 for file in files_to_upload
:
431 if file[-12:] == '.orig.tar.gz' and not include_orig_tar_gz
:
433 sys
.stdout
.write("D: Filename: %s\n" % file)
434 sys
.stdout
.write("D: Suffix: %s\n\n" % file[-12:])
436 "Package includes an .orig.tar.gz file although"
437 " the debian revision suggests\n"
438 "that it might not be required."
439 " Multiple uploads of the .orig.tar.gz may be\n"
440 "rejected by the upload queue management software.\n")
442 file[-7:] == '.tar.gz' and not include_tar_gz
443 and not include_orig_tar_gz
):
445 sys
.stdout
.write("D: Filename: %s\n" % file)
446 sys
.stdout
.write("D: Suffix: %s\n" % file[-7:])
448 "Package includes a .tar.gz file although"
449 " the version suggests that it might\n"
451 " Multiple uploads of the .tar.gz may be rejected by the\n"
452 "upload queue management software.\n")
454 distribution
= changes
.get('distribution')
455 allowed_distributions
= config
.get(host
, 'allowed_distributions')
456 if distribution
and allowed_distributions
:
459 "D: Checking: distribution %s matches %s\n"
460 % (distribution
, allowed_distributions
))
461 if not re
.match(allowed_distributions
, distribution
):
462 raise dputhelper
.DputUploadFatalException(
463 "Error: uploading files for distribution %s to %s"
465 % (distribution
, host
))
468 sys
.stdout
.write("D: File to upload: %s\n" % change_file
)
469 files_to_upload
.append(change_file
)
471 return files_to_upload
474 def print_config(config
, debug
):
475 """ Print the configuration and exit. """
476 sys
.stdout
.write("\n")
477 config
.write(sys
.stdout
)
478 sys
.stdout
.write("\n")
481 def create_upload_file(package
, host
, fqdn
, path
, files_to_upload
, debug
):
482 """ Write the log file for the upload.
484 :param package: File name of package to upload.
485 :param host: Configuration host name.
486 :param fqdn: Fully-qualified domain name of the remote host.
487 :param path: Filesystem path of the upload control file.
488 :param debug: If true, enable debugging output.
491 The upload log file is named ‘basename.hostname.upload’, where
492 “basename” is the package file name without suffix, and
493 “hostname” is the name of the host as specified in the
496 For example, uploading ‘foo_1.2.3-1_xyz.deb’ to host ‘bar’
497 will be logged to ‘foo_1.2.3-1_xyz.bar.upload’.
499 The upload log file is written to the
500 directory containing the upload control file.
503 # only need first part
504 base
= os
.path
.splitext(package
)[0]
505 logfile_name
= os
.path
.join(path
, base
+ '.' + host
+ '.upload')
507 sys
.stdout
.write("D: Writing logfile: %s\n" % logfile_name
)
509 if os
.access(logfile_name
, os
.R_OK
):
510 logfile_fd
= open(logfile_name
, 'a')
512 logfile_fd
= open(logfile_name
, 'w')
514 sys
.stderr
.write("Could not write %s\n" % logfile_name
)
517 for file in files_to_upload
:
518 entry_for_logfile
= (
519 'Successfully uploaded ' + os
.path
.basename(file) +
520 ' to ' + fqdn
+ ' for ' + host
+ '.\n')
521 logfile_fd
.write(entry_for_logfile
)
525 def run_lintian_test(changes_file
):
526 """ Run lintian on the changes file and stop if it finds errors. """
528 if os
.access(changes_file
, os
.R_OK
):
529 if os
.access("/usr/bin/lintian", os
.R_OK
):
530 old_signal
= signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
)
531 sys
.stdout
.write("Package is now being checked with lintian.\n")
532 if dputhelper
.check_call(
533 ['lintian', '-i', changes_file
]
534 ) != dputhelper
.EXIT_STATUS_SUCCESS
:
537 "Lintian says this package is not compliant"
538 " with the current policy.\n"
539 "Please check the current policy and your package.\n"
540 "Also see lintian documentation about overrides.\n")
543 signal
.signal(signal
.SIGPIPE
, old_signal
)
547 "lintian is not installed, skipping package test.\n")
549 sys
.stdout
.write("Can't read %s\n" % changes_file
)
553 def guess_upload_host(path
, filename
, config
):
554 """ Guess the host where the package should be uploaded to.
556 :param path: Directory path of the upload control file.
557 :param filename: Filename of the upload control file.
558 :param config: `ConfigParser` instance for this application.
559 :return: The hostname determined for this upload.
561 This is based on information from the upload control
567 dist_re
= re
.compile(r
'^Distribution: (.*)')
569 name_of_file
= filename
570 changes_file
= os
.path
.join(path
, name_of_file
)
573 changes_file_fd
= open(changes_file
, 'r')
575 sys
.stdout
.write("Can't open %s\n" % changes_file
)
577 lines
= changes_file_fd
.readlines()
579 match
= dist_re
.search(line
)
581 distribution
= match
.group(1)
583 # Try to guess a host based on the Distribution: field
585 for section
in config
.sections():
586 host_dists
= config
.get(section
, 'distributions')
589 for host_dist
in host_dists
.split(','):
590 if distribution
== host_dist
.strip():
593 "D: guessing host %s"
594 " based on distribution %s\n"
595 % (section
, host_dist
))
598 if len(config
.get('DEFAULT', 'default_host_main')) != 0:
600 "Trying to upload package to %s\n"
601 % config
.get('DEFAULT', 'default_host_main'))
602 return config
.get('DEFAULT', 'default_host_main')
605 "Trying to upload package to ftp-master"
606 " (ftp.upload.debian.org)\n")
610 def dinstall_caller(filename
, host
, fqdn
, login
, incoming
, debug
):
611 """ Run ‘dinstall’ for the package on the remote host.
613 :param filename: Debian package filename to install.
614 :param host: Configuration host name.
615 :param fqdn: Fully-qualified domain name of the remote host.
616 :param login: Username for login to the remote host.
617 :param incoming: Filesystem path on remote host for incoming
619 :param debug: If true, enable debugging output.
622 Run ‘dinstall’ on the remote host in test mode, and present
623 the output to the user.
625 This is so the user can see if the package would be installed
630 'ssh', '%s@%s' % (login
, fqdn
),
631 'cd', '%s' % incoming
,
632 ';', 'dinstall', '-n', '%s' % filename
]
635 "D: Logging into %s@%s:%s\n" % (login
, host
, incoming
))
636 sys
.stdout
.write("D: dinstall -n %s\n" % filename
)
637 if dputhelper
.check_call(command
) != dputhelper
.EXIT_STATUS_SUCCESS
:
639 "Error occured while trying to connect, or while"
640 " attempting to run dinstall.\n")
644 def version_check(path
, changes
, debug
):
645 """ Check if the caller has installed the package also on his system.
647 This is for testing purposes before uploading it. If not, we
654 dpkg_proc
= subprocess
.Popen(
655 'dpkg --print-architecture',
656 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
657 shell
=True, close_fds
=True)
658 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
659 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
660 dpkg_output
= dpkg_stdout
.read()
661 dpkg_architecture
= dpkg_output
.strip()
663 dpkg_stderr_output
= dpkg_stderr
.read()
665 if debug
and dpkg_stderr_output
:
667 "D: dpkg-architecture stderr output:"
668 " %r\n" % dpkg_stderr_output
)
671 "D: detected architecture: '%s'\n" % dpkg_architecture
)
673 # Get filenames of deb files:
674 for file in changes
['files'].strip().split('\n'):
675 filename
= os
.path
.join(path
, file.split()[4])
676 if filename
.endswith('.deb'):
678 sys
.stdout
.write("D: Debian Package: %s\n" % filename
)
679 dpkg_proc
= subprocess
.Popen(
680 'dpkg --field %s' % filename
,
681 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
682 shell
=True, close_fds
=True)
683 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
684 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
685 dpkg_output
= dpkg_stdout
.read()
687 dpkg_fields
= email
.parser
.HeaderParser().parsestr(dpkg_output
)
688 dpkg_stderr_output
= dpkg_stderr
.read()
690 if debug
and dpkg_stderr_output
:
692 "D: dpkg stderr output:"
693 " %r\n" % dpkg_stderr_output
)
696 and dpkg_fields
['architecture'] not in [
697 'all', dpkg_architecture
]):
700 "D: not install-checking %s due to arch mismatch\n"
703 package_name
= dpkg_fields
['package']
704 version_number
= dpkg_fields
['version']
707 "D: Package to Check: %s\n" % package_name
)
710 "D: Version to Check: %s\n" % version_number
)
711 files_to_check
.append((package_name
, version_number
))
713 for file, version_to_check
in files_to_check
:
715 sys
.stdout
.write("D: Name of Package: %s\n" % file)
716 dpkg_proc
= subprocess
.Popen(
718 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
719 shell
=True, close_fds
=True)
720 dpkg_stdout
= dputhelper
.make_text_stream(dpkg_proc
.stdout
)
721 dpkg_stderr
= dputhelper
.make_text_stream(dpkg_proc
.stderr
)
722 dpkg_output
= dpkg_stdout
.read()
724 dpkg_fields
= email
.parser
.HeaderParser().parsestr(dpkg_output
)
725 dpkg_stderr_output
= dpkg_stderr
.read()
727 if debug
and dpkg_stderr_output
:
729 "D: dpkg stderr output:"
730 " %r\n" % dpkg_stderr_output
)
731 if 'version' in dpkg_fields
:
732 installed_version
= dpkg_fields
['version']
735 "D: Installed-Version: %s\n" % installed_version
)
738 "D: Check-Version: %s\n" % version_to_check
)
739 if installed_version
!= version_to_check
:
741 "Package to upload is not installed, but it appears"
742 " you have an older version installed.\n")
745 "Uninstalled Package. Test it before uploading it.\n")
749 def execute_command(command
, position
, debug
=False):
750 """ Run a command that the user-defined in the config_file.
752 :param command: Command line to execute.
753 :param position: Position of the command: 'pre' or 'post'.
754 :param debug: If true, enable debugging output.
759 sys
.stdout
.write("D: Command: %s\n" % command
)
760 if subprocess
.call(command
, shell
=True):
761 raise dputhelper
.DputUploadFatalException(
762 "Error: %s upload command failed." % position
)
765 def check_upload_logfile(
766 changes_file
, host
, fqdn
,
767 check_only
, call_lintian
, force_upload
, debug
):
768 """ Check if the user already put this package on the specified host.
770 :param changes_file: Filesystem path of upload control file.
771 :param host: Configuration host name.
772 :param fqdn: Fully-qualified domain name of the remote host.
773 :param check_only: If true, no upload is requested.
774 :param call_lintian: If true, a Lintian invocation is requested.
775 :param force_upload: If true, don't check the upload log file.
776 :param debug: If true, enable debugging output.
781 upload_logfile
= changes_file
[:-8] + '.' + host
+ '.upload'
782 if not check_only
and not force_upload
:
783 if not os
.path
.exists(upload_logfile
):
786 fd_logfile
= open(upload_logfile
)
788 sys
.stdout
.write("Couldn't open %s\n" % upload_logfile
)
790 for line
in fd_logfile
.readlines():
791 if line
.find(fqdn
) != -1:
795 "Package has already been uploaded to %s on %s\n"
797 sys
.stdout
.write("Nothing more to do for %s\n" % changes_file
)
801 def make_usage_message():
802 """ Make the program usage help message. """
803 text
= textwrap
.dedent("""\
804 Usage: dput [options] [host] <package(s).changes>
805 Supported options (see man page for long forms):
806 -c: Config file to parse.
807 -d: Enable debug messages.
808 -D: Run dinstall after upload.
809 -e: Upload to a delayed queue. Takes an argument from 0 to 15.
811 -h: Display this help message.
812 -H: Display a list of hosts from the config file.
813 -l: Run lintian before upload.
814 -U: Do not write a .upload file after uploading.
815 -o: Only check the package.
816 -p: Print the configuration.
817 -P: Use passive mode for ftp uploads.
818 -s: Simulate the upload only.
819 -u: Don't check GnuPG signature.
820 -v: Display version information.
821 -V: Check the package version and then upload it.
827 """ Main function, no further comment needed. :) """
831 check_version
= config_print
= force_upload
= 0
832 call_lintian
= no_upload_log
= config_host_list
= 0
838 unsigned_upload
= False
842 progname
= dputhelper
.get_progname()
843 version
= dputhelper
.get_distribution_version()
845 # Parse Command Line Options.
846 (opts
, args
) = dputhelper
.getopt(
848 'c:dDe:fhHlUopPsuvV', [
849 'debug', 'dinstall', 'check-only',
850 'check-version', 'config=', 'force', 'help',
851 'host-list', 'lintian', 'no-upload-log',
852 'passive', 'print', 'simulate', 'unchecked',
853 'delayed=', 'version'])
854 for option
, arg
in opts
:
855 if option
in ('-h', '--help'):
856 sys
.stdout
.write(make_usage_message())
858 elif option
in ('-v', '--version'):
859 sys
.stdout
.write("{progname} {version}\n".format(
860 progname
=progname
, version
=version
))
862 elif option
in ('-d', '--debug'):
864 elif option
in ('-D', '--dinstall'):
866 elif option
in ('-c', '--config'):
868 elif option
in ('-f', '--force'):
870 elif option
in ('-H', '--host-list'):
872 elif option
in ('-l', '--lintian'):
874 elif option
in ('-U', '--no-upload-log'):
876 elif option
in ('-o', '--check-only'):
878 elif option
in ('-p', '--print'):
880 elif option
in ('-P', '--passive'):
882 elif option
in ('-s', '--simulate'):
884 elif option
in ('-u', '--unchecked'):
885 unsigned_upload
= True
886 elif option
in ('-e', '--delayed'):
887 if arg
in map(str, range(16)):
891 "Incorrect delayed argument,"
892 " dput only understands 0 to 15.\n")
894 elif option
in ('-V', '--check_version'):
897 # Always print the version number in the debug output
898 # so that in case of bugreports, we know which version
899 # the user has installed
902 "D: {progname} {version}\n".format(
903 progname
=progname
, version
=version
))
905 # Try to get the login from the enviroment
906 if 'USER' in os
.environ
:
907 login
= os
.environ
['USER']
909 sys
.stdout
.write("D: Login: %s\n" % login
)
911 sys
.stdout
.write("$USER not set, will use login information.\n")
912 # Else use the current username
913 login
= pwd
.getpwuid(os
.getuid())[0]
915 sys
.stdout
.write("D: User-ID: %s\n" % os
.getuid())
916 sys
.stdout
.write("D: Login: %s\n" % login
)
918 # Start Config File Parsing.
919 config
= read_configs(config_file
, debug
)
922 print_config(config
, debug
)
928 "Default Method: %s\n"
929 "\n" % config
.get('DEFAULT', 'method'))
930 for section
in config
.sections():
932 if config
.get(section
, 'distributions'):
934 ", distributions: %s" %
935 config
.get(section
, 'distributions'))
937 "%s => %s (Upload method: %s%s)\n" % (
939 config
.get(section
, 'fqdn'),
940 config
.get(section
, 'method'),
942 sys
.stdout
.write("\n")
945 # Process further command line options.
948 "No package or host has been provided, see dput -h\n")
950 elif len(args
) == 1 and not check_only
:
951 package_to_upload
= args
[0:]
956 "D: Checking if a host was named"
957 " on the command line.\n")
958 if config
.has_section(args
[0]):
960 sys
.stdout
.write("D: Host %s found in config\n" % args
[0])
961 # Host was also named, so only the rest will be a list
962 # of packages to upload.
963 preferred_host
= args
[0]
964 package_to_upload
= args
[1:]
966 not config
.has_section(args
[0])
967 and not args
[0].endswith('.changes')):
968 sys
.stderr
.write("No host %s found in config\n" % args
[0])
969 if args
[0] == 'gluck_delayed':
971 The delayed upload queue has been moved back to
972 ftp-master (aka ftp.upload.debian.org).
977 sys
.stdout
.write("D: No host named on command line.\n")
978 # Only packages have been named on the command line.
980 package_to_upload
= args
[0:]
983 sys
.stdout
.write("D: Checking for the package name.\n")
984 if config
.has_section(args
[0]):
985 sys
.stdout
.write("D: Host %s found in config.\n" % args
[0])
986 preferred_host
= args
[0]
987 package_to_upload
= args
[1:]
988 elif not config
.has_section(args
[0]):
989 sys
.stdout
.write("D: No host %s found in config\n" % args
[0])
990 package_to_upload
= args
[0:]
992 upload_methods
= import_upload_functions()
994 # Run the same checks for all packages that have been given on
996 for package_name
in package_to_upload
:
997 # Check that a .changes file was given on the command line
998 # and no matching .upload file exists.
999 if package_name
[-8:] != '.changes':
1001 "Not a .changes file.\n"
1002 "Please select a .changes file to upload.\n")
1003 sys
.stdout
.write("Tried to upload: %s\n" % package_name
)
1006 # Construct the package name for further usage.
1007 path
, name_of_package
= os
.path
.split(package_name
)
1011 # Define the host to upload to.
1012 if preferred_host
== '':
1013 host
= guess_upload_host(path
, name_of_package
, config
)
1015 host
= preferred_host
1016 if config
.get(host
, 'method') == 'local':
1019 fqdn
= config
.get(host
, 'fqdn')
1021 # Check if we already did this upload or not
1022 check_upload_logfile(
1023 package_name
, host
, fqdn
,
1024 check_only
, call_lintian
, force_upload
, debug
)
1026 # Run the change file tests.
1027 files_to_upload
= verify_files(
1028 path
, name_of_package
, host
,
1029 config
, check_only
, check_version
, unsigned_upload
, debug
)
1031 # Run the lintian test if the user asked us to do so.
1034 config
.getboolean(host
, 'run_lintian') == 1):
1035 run_lintian_test(os
.path
.join(path
, name_of_package
))
1038 "Warning: The option -o does not automatically include \n"
1039 "a lintian run any more. Please use the option -ol if \n"
1040 "you want to include running lintian in your checking.\n")
1042 # don't upload, skip to the next item
1044 sys
.stdout
.write("Package checked by dput.\n")
1047 # Pre-Upload Commands
1048 if len(config
.get(host
, 'pre_upload_command')) != 0:
1050 command
= config
.get(host
, 'pre_upload_command')
1051 execute_command(command
, position
, debug
)
1053 # Check the upload methods that we have as default and per host
1056 "D: Default Method: %s\n"
1057 % config
.get('DEFAULT', 'method'))
1058 if config
.get('DEFAULT', 'method') not in upload_methods
:
1060 "Unknown upload method: %s\n"
1061 % config
.get('DEFAULT', 'method'))
1065 "D: Host Method: %s\n" % config
.get(host
, 'method'))
1066 if config
.get(host
, 'method') not in upload_methods
:
1068 "Unknown upload method: %s\n"
1069 % config
.get(host
, 'method'))
1072 # Inspect the Config and set appropriate upload method
1073 if not config
.get(host
, 'method'):
1074 method
= config
.get('DEFAULT', 'method')
1076 method
= config
.get(host
, 'method')
1078 # Check now the login and redefine it if needed
1080 len(config
.get(host
, 'login')) != 0 and
1081 config
.get(host
, 'login') != 'username'):
1082 login
= config
.get(host
, 'login')
1085 "D: Login %s from section %s used\n" % (login
, host
))
1087 len(config
.get('DEFAULT', 'login')) != 0 and
1088 config
.get('DEFAULT', 'login') != 'username'):
1089 login
= config
.get('DEFAULT', 'login')
1091 sys
.stdout
.write("D: Default login %s used\n" % login
)
1095 "D: Neither host %s nor default login used. Using %s\n"
1098 incoming
= config
.get(host
, 'incoming')
1100 # if delay_upload wasn't passed via -e/--delayed
1101 if delay_upload
is None:
1102 delay_upload
= config
.get(host
, 'delayed')
1103 if not delay_upload
:
1104 delay_upload
= config
.get('DEFAULT', 'delayed')
1107 if int(delay_upload
) == 0:
1108 sys
.stdout
.write("Uploading to DELAYED/0-day.\n")
1109 if incoming
[-1] == '/':
1113 incoming
+= first_char
+ 'DELAYED/' + delay_upload
+ '-day'
1114 delayed
= ' [DELAYED/' + delay_upload
+ ']'
1118 # Do the actual upload
1121 "Uploading to %s%s (via %s to %s):\n"
1122 % (host
, delayed
, method
, fqdn
))
1124 sys
.stdout
.write("D: FQDN: %s\n" % fqdn
)
1125 sys
.stdout
.write("D: Login: %s\n" % login
)
1126 sys
.stdout
.write("D: Incoming: %s\n" % incoming
)
1127 progress
= config
.getint(host
, 'progress_indicator')
1128 if not os
.isatty(1):
1132 fqdn
, port
= fqdn
.rsplit(":", 1)
1135 ftp_mode
= config
.getboolean(host
, 'passive_ftp')
1136 if ftp_passive_mode
== 1:
1139 sys
.stdout
.write("D: FTP port: %s\n" % port
)
1141 sys
.stdout
.write("D: Using passive ftp\n")
1143 sys
.stdout
.write("D: Using active ftp\n")
1144 upload_methods
[method
](
1145 fqdn
, login
, incoming
,
1146 files_to_upload
, debug
, ftp_mode
,
1147 progress
=progress
, port
=port
)
1148 elif method
== 'scp':
1149 if debug
and config
.getboolean(host
, 'scp_compress'):
1150 sys
.stdout
.write("D: Setting compression for scp\n")
1151 scp_compress
= config
.getboolean(host
, 'scp_compress')
1152 ssh_config_options
= [
1155 config
.get(host
, 'ssh_config_options').split('\n'))
1159 "D: ssh config options:"
1161 + "\n ".join(ssh_config_options
)
1163 upload_methods
[method
](
1164 fqdn
, login
, incoming
,
1165 files_to_upload
, debug
, scp_compress
,
1168 upload_methods
[method
](
1169 fqdn
, login
, incoming
,
1170 files_to_upload
, debug
, 0, progress
=progress
)
1171 # Or just simulate it.
1173 for file in files_to_upload
:
1175 "Uploading with %s: %s to %s:%s\n"
1176 % (method
, file, fqdn
, incoming
))
1178 # Create the logfile after the package has
1179 # been put into the archive.
1181 if not no_upload_log
:
1183 name_of_package
, host
, fqdn
, path
,
1184 files_to_upload
, debug
)
1185 sys
.stdout
.write("Successfully uploaded packages.\n")
1187 sys
.stdout
.write("Simulated upload.\n")
1189 # Run dinstall if the user asked us to do so.
1191 sys
.stdout
.write("D: dinstall: %s\n" % dinstall
)
1193 "D: Host Config: %s\n"
1194 % config
.getboolean(host
, 'run_dinstall'))
1195 if config
.getboolean(host
, 'run_dinstall') == 1 or dinstall
:
1198 name_of_package
, host
, fqdn
, login
, incoming
, debug
)
1200 sys
.stdout
.write("Will run dinstall now.\n")
1202 # Post-Upload Command
1203 if len(config
.get(host
, 'post_upload_command')) != 0:
1205 command
= config
.get(host
, 'post_upload_command')
1206 execute_command(command
, position
, debug
)
1211 if __name__
== '__main__':
1214 except KeyboardInterrupt:
1215 sys
.stdout
.write("Exiting due to user interrupt.\n")
1217 except dputhelper
.DputException
as e
:
1218 sys
.stderr
.write("%s\n" % e
)
1226 # vim: fileencoding=utf-8 filetype=python :