dput-ng-1.7/0000755000000000000000000000000012241261643007626 5ustar dput-ng-1.7/debian/0000755000000000000000000000000012241262032011041 5ustar dput-ng-1.7/debian/changelog0000644000000000000000000002020712241156024012717 0ustar dput-ng (1.7) unstable; urgency=low * The "Get two birds stoned at once." release [ Paul Tagliamonte ] * Fix up default host argument. This caused the first block with the default_host_main argument set to be considered the default host, rather than the value of default_host_main. (Closes: #729280) * Add experimental support for `dud` files, for Debile. [ Arno Töll ] * Be more explicit how to override dcut dm's UID check -- Paul Tagliamonte Thu, 14 Nov 2013 09:20:03 -0500 dput-ng (1.6) unstable; urgency=low * The "Well, shit, release names are hard too" release [ Paul Tagliamonte ] * Add BYHAND handling. (Closes: #719975) * Change default profile to "boring" rather than "debian". This caused non-Debian targets to target Debian codenames. Which sucks. (Closes: #718384) [ James Page ] * Add in codenames for Ubuntu. (Closes: #714103) [ Luca Falavigna ] * Fix path of local profiles in bash-completion script. * Install dput.commands.contrib package. * Add bash completion for dcut. * Add support for Deb-o-Matic builddep command. -- Paul Tagliamonte Sun, 08 Sep 2013 14:47:51 -0400 dput-ng (1.5) unstable; urgency=low * The "Command line flags are hard" release [ Paul Tagliamonte ] * Add per-host loading of command files by partially parsing command line arguments. * Add disallowed_distributions. * Apply a file name heuristic to tell whether a positional is a host or a target (Closes: #710150). * Add codename groups to vital targets, limit security uploads to the right servers (Closes: #708575). [ Luca Falavigna ] * Implement dcut commands for Deb-o-Matic. [ Bernhard R. Link ] * Reimplement the SFTP uploader [ Arno Töll ] * Be more precise about our configuration file inheritance in dput(5). * Fix "dcut: manpage and --help talk about -U and --upload" by replacing those parts in the text by the 'upload' command (Closes: #699812) * Make the login name determination more portable (Closes: #709831) * Fix "Allow to give out dm permissions without using local keyring" by * documenting the --force option in the man page (Closes: #711057) [ Wolodja Wentland ] * Allow uploads to mentors to target every distribution [ Sebastian Ramacher ] * Add a space in --debug's help message. [ Thomas Preud'homme ] * Fixed a typo (DEBNAME → DEBFULLNAME) in the dcut(1) manpage. -- Paul Tagliamonte Thu, 20 Jun 2013 21:10:33 -0400 dput-ng (1.4) unstable; urgency=low * The "Wait, what was so bad about SCP, again?" release [ Arno Töll ] * Really fix #696659 by making sure the command line tool uses the most recent version of the library. * Mark several fields to be required in profiles (incoming, method) * Fix broken tests. * Do not run the check-debs hook in our mentors.d.n profile * Fix "[dcut] dm bombed out" by using the profile key only when defined (Closes: #698232) * Parse the gecos field to obtain the user name / email address from the local system when DEBFULLNAME and DEBEMAIL are not set. * Fix "dcut reschedule sends "None-day" to ftp-master if the delay is not specified" by forcing the corresponding parameter (Closes: #698719) [ Luca Falavigna ] * Implement default_keyid option. This is particularly useful with multiple GPG keys, so dcut is aware of which one to use. * Make scp uploader aware of "port" configuration option. [ Paul Tagliamonte ] * Hack around Launchpad's SFTP implementation. We musn't stat *anything*. "Be vewy vewy quiet, I'm hunting wabbits" (Closes: #696558). * Rewrote the test suite to actually test the majority of the codepaths we take during an upload. Back up to 60%. * Added a README for the twitter hook, Thanks to Sandro Tosi for the bug, and Gergely Nagy for poking me about it. (Closes: #697768). * Added a doc for helping folks install hooks into dput-ng (Closes: #697862). * Properly remove DEFAULT from loadable config blocks. (Closes: #698157). * Allow upload of more then one file. Thanks to Iain Lane for the suggestion. (Closes: #698855). [ Bernhard R. Link ] * allow empty incoming dir to upload directly to the home directory [ Sandro Tosi ] * Install example hooks (Closes: #697767). -- Arno Töll Tue, 29 Jan 2013 21:50:13 +0100 dput-ng (1.3) unstable; urgency=low * The "we're so proud of our work, we need to let everyone know" release [ Paul Tagliamonte ] * Avoid failing on upload if a pre/post upload hook is missing from the Filesystem. Thanks to Moritz Mühlenhoff for the report. (Closes: #696659) * Adjust Homepage: to point to our spiffy debian.net alias, rather then my people.debian. * Add in experiemental clojure support via clojurepy hackery. It's amazingly cool, really. Thanks to Paul Tagliamonte for the extremely nice patch. Well done. [ Arno Töll ] * Fix "dcut raises FtpUploadException" by correctly initializing the uploader classes from dcut (Closes: #696467) -- Paul Tagliamonte Wed, 26 Dec 2012 09:30:06 -0500 dput-ng (1.2) unstable; urgency=low * The "Well you're smoking with the patch on." release [ Salvatore Bonaccorso ] * Add bash completions for dput-ng (Closes: #695412). + Add bash completions for dput-ng based on traditional dput package. + Add bash-completion to Build-Depends and Recommends. + Use bash-completion Debhelper addon to install the bash completions. [ Paul Tagliamonte ] * Hijacking the package (set maintainer to our new alioth list. Thanks, alioth maintainers!) * Add in a script to set the default profile depending on the building distro. (debian/rules, debian/bin/adjust-vendor) * Fix a bug where meta-class info won't be loaded if the config file has the same name. * Add an Ubuntu upload target. Thanks to Benjamin Drung for the suggestion, and Jeremy for the bug. (Closes: #695490) * Drop the hard dependency on validictory. Thanks to Jakub Wilk for the patch. (Closes: #695516) * Added .udeb detection to the check debs hook. [ Arno Töll ] * Catch the correct exception falling out of bin/dcut * Fix the dput manpages to use --uid rather then the old --dm flag. * Fix the CLI flag registration by setting required=True in cancel and upload. * Move make_delayed_upload above the logging call for sanity's sake. * Fix "connects to the host even with -s" (Closes: #695347) Arguably we did so as a design decision, but I can see how this confuses people. Hence, do not establish a network connection anymore using the -s(imulation) mode, unless -s was specified twice (-ss) + While I was on it, do so for -o (check-only) uploads as well * More distro mappings for check_protected_distributions. -- Arno Töll Thu, 13 Dec 2012 17:11:27 +0100 dput-ng (1.1) unstable; urgency=low * The "monday afternoon's a rather awkward day to get so drunk" release [ Arno Töll ] * Clarify copyight for dput/changes.py: It's MIT, not GPL * Install dput.cf(5) to the correct man page directory. * Fix a bunch of grammar and Python styling issues - thanks to Jakub Wilk for the pointers. * Fix "FTBFS: a2x call -> missing build dependency on docbook-xsl" add docbook-xsl to the build dependencies. Thanks gregoa for spotting it and providing a fix (Closes: #694983) [ Ansgar Burchardt ] * debian/control: Add Vcs-{Browser,Git} fields. * debian/control: Conflicts: dput instead of Breaks. * debian/control: Don't use hyphens in the package description. [ Paul Tagliamonte ] * debian/copyright: move to DEP5. * debian/control: remove senseless multiarch tags * debian/control: add Homepage * debian/control: since we conflict with dput-old, mark this as priority extra. * other random pythonic fixes pointed out by Jakub. + pep8'd `klass' to `cls' + clean the .coverage file + remove the except:\n\tpass -- Paul Tagliamonte Tue, 04 Dec 2012 09:19:39 -0500 dput-ng (1.0) unstable; urgency=low * Initial release (Closes: #691624) -- Arno Töll Mon, 26 Nov 2012 17:41:41 +0100 dput-ng-1.7/debian/dput-ng-doc.links0000644000000000000000000000017612114414240014227 0ustar usr/share/doc/dput-ng-doc/html usr/share/doc/dput-ng/html usr/share/doc/dput-ng-doc/html usr/share/doc/python-dput/html dput-ng-1.7/debian/dput-ng.dirs0000644000000000000000000000044512114414240013304 0ustar etc/dput.d/hooks etc/dput.d/commands etc/dput.d/interfaces etc/dput.d/metas etc/dput.d/hooks etc/dput.d/profiles etc/dput.d/uploaders usr/share/dput-ng/commands usr/share/dput-ng/interfaces usr/share/dput-ng/metas usr/share/dput-ng/hooks usr/share/dput-ng/profiles usr/share/dput-ng/uploaders dput-ng-1.7/debian/rules0000755000000000000000000000211712114414240012121 0ustar #!/usr/bin/make -f MANPAGES := $(patsubst docs/man/%.man, debian/man/%, $(wildcard docs/man/*.man)) VENDOR = $(shell dpkg-vendor --query vendor) %: dh $@ --with bash-completion,python2,sphinxdoc override_dh_auto_clean: dh_auto_clean rm -rvf ./docs/_build ./*.egg-info ./build .coverage rm -f $(MANPAGES) rm -rf debian/man override_dh_auto_test: # We don't test for all versions, because we don't support all versions. # Die, python2.6, die! dput (oldschool) will work fine with 2.6, use that # :) ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) nosetests endif override_dh_auto_build: dh_auto_build make -C docs html override_dh_auto_install: dh_auto_install rm -vf ./debian/tmp/usr/lib/*/dist-packages/dput*egg-info/SOURCES.txt override_dh_install: dh_install debian/bin/adjust-vendor $(VENDOR) debian/man: mkdir $@ $(MANPAGES): debian/man a2x --doctype manpage --format manpage -D $(dir $@) \ $(patsubst debian/man/%, docs/man/%.man, $@) override_dh_installman: $(MANPAGES) dh_installman --language=C override_dh_installexamples: dh_installexamples examples/* dput-ng-1.7/debian/bin/0000755000000000000000000000000012114414240011610 5ustar dput-ng-1.7/debian/bin/adjust-vendor0000755000000000000000000000223212114414240014322 0ustar #!/usr/bin/env python # Copyright (c) Paul Tagliamonte , 2012 under the terms # and conditions of the dput-ng project it's self. import json import sys PROFILE_PATH = "./debian/dput-ng/etc/dput.d/profiles" distrib = sys.argv[1] default_target_vendor_table = { "Debian": "ftp-master", "Ubuntu": "ubuntu" } target = default_target_vendor_table[distrib] fp = "%s/%s.json" % (PROFILE_PATH, target) print """I: === Adjusting Vendor === I: I: Hi there, log viewer. I: I: This simple script takes the argument (which should come from dpkg-vendor), I: and uses it to modify the default profile to add an entry (default_host_main) I: to the default profile, so that dput correctly sets the default host. I: I: Vendor: {vendor} I: Profile default: {target} I: Path: {fp} I: I: If you're currently debugging why the default target is wonky, check I: me out (debian/bin/adjust-vendor) I: I: Fondly, I: -- the dput-ng team""".format(vendor=distrib, target=target, fp=fp) obj = {} with open(fp, 'r') as fd: obj = json.load(fd) obj['default_host_main'] = target json.dump(obj, open(fp, 'w'), sort_keys=True, indent=4) dput-ng-1.7/debian/dput-ng.bash-completion0000644000000000000000000000707712171142463015447 0ustar #-*- mode: shell-script;-*- # Debian dput(1) completion # Copyright 2002 Roland Mas # Copyright 2012 Salvatore Bonaccorso # Copyright 2013 Luca Falavigna have dput && _dput() { local cur prev options paroptions delayed_options hosts COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} options='-c --config -d --debug -D --dinstall -e --delayed \ -F --full-upload-log -f --force -l --lintian -o --check-only \ -P --passive -s --simulate -U --no-upload-log -u --unchecked \ -v --version -V --check-version' hosts=$( { grep "^\[.*\]" $HOME/.dput.cf 2> /dev/null | tr -d [] || /bin/true grep "^\[.*\]" /etc/dput.cf 2> /dev/null | tr -d [] || /bin/true for file in $(ls $HOME/.dput.d/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true for file in $(ls /etc/dput.d/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true for file in $(ls /usr/share/dput-ng/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true } | grep -v '^DEFAULT$' | sort -u) paroptions="$options $hosts" case $prev in --delayed|-e) delayed_options='0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15' COMPREPLY=( $( compgen -W "$delayed_options" | grep ^$cur ) ) ;; --config|-c) COMPREPLY=( $( compgen -o filenames -G "$cur*" ) ) ;; *) COMPREPLY=( $( compgen -G "${cur}*.changes" compgen -G "${cur}*.asc" compgen -G "${cur}*.sig" compgen -W "$paroptions" | grep "^$cur" ) ) ;; esac return 0 } [ "$have" ] && complete -F _dput -o filenames -o plusdirs dput have dcut && _dcut() { local cur prev options paroptions hosts COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} options='-c --config -d --debug -f --force -k --keyid -m --maintainer \ -O --output -P --passive -s --simulate -v --version' hosts=$( { grep "^\[.*\]" $HOME/.dput.cf 2> /dev/null | tr -d [] || /bin/true grep "^\[.*\]" /etc/dput.cf 2> /dev/null | tr -d [] || /bin/true for file in $(ls $HOME/.dput.d/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true for file in $(ls /etc/dput.d/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true for file in $(ls /usr/share/dput-ng/profiles/*.json 2> /dev/null); do \ basename $file .json; \ done || /bin/true } | grep -v '^DEFAULT$' | sort -u) commands=$( { cmd="import dput\n" cmd=$cmd"try:\n" cmd=$cmd" profile = dput.profile.load_profile('$prev')\n" cmd=$cmd" cmds = dput.command.load_commands(profile)\n" cmd=$cmd"except dput.exceptions.DputConfigurationError:\n" cmd=$cmd" cmds = []\n" cmd=$cmd"print ' '.join([x.name_and_purpose()[0] for x in cmds])" echo $cmd | sed 's/\\n/\'$'\n/g' | python } | sort -u) paroptions="$options $hosts $commands" case $prev in --config|-c) COMPREPLY=( $( compgen -o filenames -G "$cur*" ) ) ;; *) COMPREPLY=( $( compgen -G "${cur}*.changes" compgen -W "$paroptions" | grep "^$cur" ) ) ;; esac return 0 } [ "$have" ] && complete -F _dcut -o filenames -o plusdirs dcut dput-ng-1.7/debian/dput-ng-doc.install0000644000000000000000000000006712114414240014554 0ustar docs/_build/html usr/share/doc/dput-ng-doc/ dput-ng-1.7/debian/gbp.conf0000644000000000000000000000006712114414240012462 0ustar [DEFAULT] pristine-tar = True debian-tag = %(version)s dput-ng-1.7/debian/dput-ng.install0000644000000000000000000000076112160717773014034 0ustar bin/* usr/bin/ skel/metas etc/dput.d/ skel/profiles etc/dput.d/ skel/hooks usr/share/dput-ng/ skel/commands usr/share/dput-ng/ skel/interfaces usr/share/dput-ng/ skel/uploaders usr/share/dput-ng/ skel/schemas usr/share/dput-ng/ skel/codenames usr/share/dput-ng/ skel/README usr/share/dput-ng/ README.md usr/share/doc/dput-ng/ dput-ng-1.7/debian/python-dput.install0000644000000000000000000000002012114414240014713 0ustar usr/lib/python* dput-ng-1.7/debian/control0000644000000000000000000000630412241257406012460 0ustar Source: dput-ng Section: devel Priority: extra Maintainer: dput-ng Maintainers Uploaders: Arno Töll , Paul Tagliamonte Build-Depends: debhelper (>= 9), python-distro-info, bash-completion, python-all (>= 2.7~), python-setuptools, python-coverage, python-debian, python-nose, python-validictory, python-paramiko, python-sphinx (>= 1.0.7+dfsg), asciidoc, libxml2-utils, docbook-xml, docbook-xsl, docbook-xsl-ns, xsltproc Standards-Version: 3.9.4 Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/dputng.git Vcs-Git: git://git.debian.org/collab-maint/dputng.git Homepage: http://dput-ng.debian.net/ X-Python-Version: >= 2.7 Package: dput-ng Architecture: all Conflicts: dput Replaces: dput Provides: dput Depends: ${misc:Depends}, ${python:Depends}, python-dput (= ${binary:Version}) Recommends: bash-completion Description: next generation Debian package upload tool dput-ng is a Debian package upload tool which provides an easy to use interface to Debian (like) package archive hosting facilities. It allows anyone who works with Debian packages to upload their work to a remote service, including Debian's ftp-master, mentors.debian.net, Launchpad or other package hosting facilities for Debian package maintainers. . dput-ng features many enhancements over dput, such as more comprehensive checks, an easy to use plugin system, and code designed to handle the numerous archives that any Debian package hacker will interact with. . dput-ng aims to be backwards compatible with dput in command-line flags, configuration files, and expected behavior. Package: python-dput Architecture: all Section: python Depends: ${misc:Depends}, ${python:Depends}, python-debian, gnupg, Recommends: lintian, python-distro-info, python-paramiko, python-validictory, openssh-client, python-distro-info, debian-keyring Description: next generation Debian package upload tool (Python library) dput-ng is a Debian package upload tool which provides an easy to use interface to Debian (like) package archive hosting facilities. It allows anyone who works with Debian packages to upload their work to a remote service, including Debian's ftp-master, mentors.debian.net, Launchpad or other package hosting facilities for Debian package maintainers. . This package provides Python library functions providing core functionality to dput-ng and tools building on top of that. This library provides upload classes, profile parser, sanity checks and helper functions required to build a dput service. Package: dput-ng-doc Architecture: all Section: doc Depends: ${misc:Depends}, ${sphinxdoc:Depends} Description: next generation Debian package upload tool (documentation) dput-ng is a Debian package upload tool which provides an easy to use interface to Debian (like) package archive hosting facilities. It allows anyone who works with Debian packages to upload their work to a remote service, including Debian's ftp-master, mentors.debian.net, Launchpad or other package hosting facilities for Debian package maintainers. . This package provides exhaustive user documentation, developer API docu- mentation and command interface documentation. dput-ng-1.7/debian/dput-ng.manpages0000644000000000000000000000001512114414240014127 0ustar debian/man/* dput-ng-1.7/debian/source/0000755000000000000000000000000012114414240012340 5ustar dput-ng-1.7/debian/source/format0000644000000000000000000000001512114414240013547 0ustar 3.0 (native) dput-ng-1.7/debian/compat0000644000000000000000000000000212114414240012236 0ustar 9 dput-ng-1.7/debian/dput-ng.links0000644000000000000000000000006112114414240013455 0ustar usr/share/dput-ng/README etc/dput.d/README dput-ng-1.7/debian/copyright0000644000000000000000000000470012114414240012774 0ustar Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 Upstream-Name: dput-ng Upstream-Contact: Arno Töll Source: http://anonscm.debian.org/gitweb/?p=collab-maint/dputng.git Files: * Copyright: 2012, Paul Tagliamonte 2012, Arno Töll License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. Files: dput/changes.py Copyright: 2008, Jonny Lamb 2010, Jan Dittberner 2012, Arno Töll 2012, Paul Tagliamonte License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.dput-ng-1.7/debian/dput-ng-doc.doc-base0000644000000000000000000000036412114414240014563 0ustar Document: dput-ng Title: dput-ng documentation Author: Paul Tagliamonte Abstract: This manual describes dput-ng's features Section: Debian Format: HTML Index: /usr/share/doc/dput-ng-doc/html/index.html Files: /usr/share/doc/dput-ng-doc/html/* dput-ng-1.7/FAQ0000644000000000000000000000706712114414240010162 0ustar ____ ____ _ _ _____ _ _ ____ _____ _ ___ | _ \| _ \| | | |_ _| | \ | |/ ___| | ___/ \ / _ \ ___ | | | | |_) | | | | | |_____| \| | | _ | |_ / _ \| | | / __| | |_| | __/| |_| | | |_____| |\ | |_| | | _/ ___ \ |_| \__ \ |____/|_| \___/ |_| |_| \_|\____| |_|/_/ \_\__\_\___/ 1) So, what's the big deal, anyway? Well, yeah, good question. dput works great, and all the authors of dput-ng really love dput a lot. So much, in fact, that we've decided to maintain 100% compatibility with the flags, config files and expected behavior of the tool. The whole idea here is that dput is starting to get a bit too subject to bitrot, and new features are hard to implement. Moreover, adding new stuff has to happen at the dput level, which is a pain for people who support non-standard stuff. We have a spiffy new "plugin" system, where plugins can be in their own package (very far away from dput's) and still work famously. 2) So, what's all this plugin nonsense about? All uploaders (stuff like sftp, scp, local, ftp, http) are implemented as "uploaders". They're registered via .json files, which declare the import path to the class -- such as `dput.uploaders.sftp.SFTPUploader`. This allows uploaders, hooks (they work just like uploaders!) to be run from dput-ng even if they're outside dput-ng's package (so, nothing stops you from writing `foopackage.barmodule.BazUploader` or `kruft.fooChecker` and using them when you need to). We think this will result in cool new hooks (since uploaders are a bit of work) being written to better check packages to be uploaded before they go too far :) 3) Is dput-ng compatible with dput configuration files? Short answer: Yes! Long answer: Well, mostly. We've chosen to re-define deprecated / outdated things. You shouldn't notice any changes, except for users of `run_dinstall'. You should also be aware that there are some *new* configuration files, by default, in /etc/dput.d/ and ~/.dput.d/. These config files help define new features and checker behavior. 4) Where does dput compatibility end? We attempted to make the binary look and feel as close to the original as we could muster -- as well as files it produces (.upload files, etc). Any issues with the interface's arguments are taken very seriously, and (with minor and documented exception) should be fixed very quickly. 5) How to make my own profile? Cool! So, profiles are not unlike dput.cf blocks, but the define the behavior of dput-ng a bit more exactly & in a fragmented way. All new config files are in JSON, and have similar override behavior to global / local dput.cf files. All you have to do is place a new profile in ~/.dput.d/profiles/ (or, for more the one user / distributing it -- /etc/dput.d/profiles) which looks similar to: $ cat ~/.dput.d/profiles/personal-archive.json { "name": "My Personal Archive", "+hooks": [ "lintian" ], "interface": "cli" } You can read more about profiles, and the config files in the documentation. 6) Hey, I can't run dput -H -p or dput -p -H they way I think it should! well, try it with plain ole' dput :) Anyway, those arguments are a bit of a special case & for debugging. All the "short" flags can't be used together -- flags like "-v", "-H", or "-p" shouldn't be used together anyway. dput-ng-1.7/tests/0000755000000000000000000000000012241261643010770 5ustar dput-ng-1.7/tests/fake_package/0000755000000000000000000000000012241261060013342 5ustar dput-ng-1.7/tests/fake_package/fnord_1.0_source.changes0000644000000000000000000000160712241261060017746 0ustar Format: 1.8 Date: Mon, 26 Nov 2012 17:41:41 +0100 Source: fnord Binary: fnord Architecture: source Version: 1.0 Distribution: precise Urgency: low Maintainer: Winston Smith Changed-By: Winston Smith Description: fnord - beard labore +1 chillwave esse pour-over sartorial Closes: 691624 Changes: fnord (1.0) precise; urgency=low . * Initial release (Closes: #691624) Checksums-Sha1: b718923ab0ef69a5d8ea2230592a8455d26ba818 479 fnord_1.0.dsc 4614ca50a16f4118c2402cf8a2c4a511584e029d 1127 fnord_1.0.tar.gz Checksums-Sha256: 15811d00011b54d6c1b1bbdedc307e99bd46ebdea542d18955f390243a0ace40 479 fnord_1.0.dsc eed4044376870e9e246267f588b3a4820acf11a6e0536d309d7a8e2d3f0d3e78 1127 fnord_1.0.tar.gz Files: 9d186f6169ded58b80f91448fc1d70b4 479 misc optional fnord_1.0.dsc 31d121b8bb6a0f3fe8a6e86c6ca6ad6b 1127 misc optional fnord_1.0.tar.gz dput-ng-1.7/tests/fake_package/fnord_1.0_source.test.upload0000644000000000000000000000205612241261060020577 0ustar 2013-11-14 18:52:16,496 - dput[11790]: uploader.invoke_dput - Uploading fnord using local to test (host: test; directory: /dev/null) 2013-11-14 18:52:16,497 - dput[11790]: hook.run_hook - running checksum: verify checksums before uploading 2013-11-14 18:52:16,500 - dput[11790]: hook.run_hook - running suite-mismatch: check the target distribution for common errors 2013-11-14 18:52:16,503 - dput[11790]: hook.run_hook - running check-debs: makes sure the upload contains a binary package 2013-11-14 18:52:16,506 - dput[11790]: hook.run_hook - running supported-distribution: check whether the target distribution is currently supported (using distro-info) 2013-11-14 18:52:16,509 - dput[11790]: distro_info_checks.check_supported_distribution - {u'known': [u'release'], u'allowed': [u'release']} 2013-11-14 18:52:16,512 - dput[11790]: uploader.invoke_dput - Uploading fnord_1.0.dsc 2013-11-14 18:52:16,515 - dput[11790]: uploader.invoke_dput - Uploading fnord_1.0.tar.gz 2013-11-14 18:52:16,519 - dput[11790]: uploader.invoke_dput - Uploading fnord_1.0_source.changes dput-ng-1.7/tests/fake_package/fnord_1.0.tar.gz0000644000000000000000000000214712241261057016171 0ustar Xmo6W0?:z(}+cnE}IfG翤\ ==x\$r1\,$E#tM ?[̖A_[☙?AgCok2Q|3-  G8~#½iH~lJv# k 5C+R]֨̓<mA+S+k+~BE/ԞdWVGz H0jHudqNOFѹt7dntEeym16-ü_񣮰%'J&9e:l"'gM[ - 8+^1ÅQZ>K/:4$G p+WҎ"dWA WJ_H]R Z:\[/u0W>I7 A:8#4o_j7$oHI3 F?<5SlQXQy$-aa֌kS[хj^U%z?Gz#v-ameƕD;Тk`.Z91 .%Mִe]O[5GӼ0.&qI(2t 88{fԵԵ5JͱgDI+p-76$ ?el+mYH&1rC'Pݿ?N2 "2gMp Sϭt#=ghI@h4 F@Q(h4   ֪.UPdput-ng-1.7/tests/fake_package/fnord_1.0.dsc0000644000000000000000000000073712241261057015540 0ustar Format: 3.0 (native) Source: fnord Binary: fnord Architecture: all Version: 1.0 Maintainer: Winston Smith Standards-Version: 3.9.4 Build-Depends: debhelper (>= 9) Package-List: fnord deb misc optional Checksums-Sha1: 4614ca50a16f4118c2402cf8a2c4a511584e029d 1127 fnord_1.0.tar.gz Checksums-Sha256: eed4044376870e9e246267f588b3a4820acf11a6e0536d309d7a8e2d3f0d3e78 1127 fnord_1.0.tar.gz Files: 31d121b8bb6a0f3fe8a6e86c6ca6ad6b 1127 fnord_1.0.tar.gz dput-ng-1.7/tests/fake_package/fake-package-1.0/0000755000000000000000000000000012114414240016134 5ustar dput-ng-1.7/tests/fake_package/fake-package-1.0/foo0000644000000000000000000000000012114414240016630 0ustar dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/0000755000000000000000000000000012203761276017373 5ustar dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/changelog0000644000000000000000000000022412203761276021243 0ustar fnord (1.0) precise; urgency=low * Initial release (Closes: #691624) -- Winston Smith Mon, 26 Nov 2012 17:41:41 +0100 dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/rules0000755000000000000000000000003512114414240020434 0ustar #!/usr/bin/make -f %: dh $@ dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/fnord.install0000644000000000000000000000004012114414240022050 0ustar foo usr/share/fnord/foo dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/control0000644000000000000000000000151512114414240020763 0ustar Source: fnord Section: misc Priority: optional Maintainer: Winston Smith Build-Depends: debhelper (>= 9), Standards-Version: 3.9.4 Package: fnord Depends: ${misc:Depends} Architecture: all Description: beard labore +1 chillwave esse pour-over sartorial organic sriracha et 3 wolf moon helvetica mixtape. Biodiesel master cleanse magna, seitan ea banh mi hoodie ullamco odio incididunt viral nisi bicycle rights hella laboris. Ut placeat artisan incididunt, high life YOLO odio. . Velit you probably haven't heard of them craft beer. Terry richardson quis kogi, nihil synth truffaut banjo consequat fixie. Street art cray lo-fi messenger bag mixtape gastropub, nulla cardigan mustache eu tousled keytar. Cillum hashtag nesciunt, fashion axe dolor ex selvage DIY meggings pug direct trade food truck small batch. dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/source/0000755000000000000000000000000012114414240020656 5ustar dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/source/format0000644000000000000000000000001512114414240022065 0ustar 3.0 (native) dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/compat0000644000000000000000000000000212114414240020554 0ustar 9 dput-ng-1.7/tests/fake_package/fake-package-1.0/debian/copyright0000644000000000000000000000006412114414240021311 0ustar If anyone tries to copyright this, I'll stroke out. dput-ng-1.7/tests/test_overrides.py0000644000000000000000000000423312160717773014417 0ustar #!/usr/bin/env python # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.profile import parse_overrides from dput.exceptions import DputConfigurationError def test_parse_overrides(): test_cases = [ ({'foo': [['bar']]}, ['foo=bar']), ({'foo': [None]}, ['-foo']), ({'+foo': [['bar']]}, ['+foo=bar']), ({'foo': [None, ['1']]}, ['-foo', 'foo=1']), ({'foo': {'bar': {'baz': [['paultag.is.a.god']]}}}, ['foo.bar.baz=paultag.is.a.god']), ({'foo': {'bar': [['1'], ['2'], ['3']]}, 'foo2': [['4']]}, ['foo.bar=1', 'foo.bar=2', 'foo.bar=3', 'foo2=4']), ({'foo': {'bar': [['True=True'], ['foo=False', 'obj2']]}, 'foo2': [['bar']]}, ['foo.bar = "True=True"', "foo.bar='foo'=False 'obj2'", 'foo2=bar']) ] bad_test_cases = [ (None, ['invalid']), (None, ['foo=bar', 'foo=bar', 'foo.bar=bar']), (None, ['foo.bar=1', 'foo.bar.baz=1']) ] for (expectation, case) in test_cases: print(parse_overrides(case)) assert(expectation == parse_overrides(case)) for (expectation, case) in bad_test_cases: try: parse_overrides(case) raise Exception("Bad test case: %s" % (case)) except DputConfigurationError: pass dput-ng-1.7/tests/test_upload.py0000644000000000000000000000171712241257766013706 0ustar from dput.util import run_command from dput import upload from dput.exceptions import UploadException import dput.core import os.path import os dput.core.CONFIG_LOCATIONS = { os.path.abspath("./tests/dputng"): 0 } # Isolate. def _build_fnord(): popdir = os.path.abspath(os.getcwd()) os.chdir("tests/fake_package/fake-package-1.0") stdout, stederr, ret = run_command("dpkg-buildpackage -us -uc -S") if os.path.exists("../fnord_1.0_source.test.upload"): os.unlink("../fnord_1.0_source.test.upload") os.chdir(popdir) return os.path.abspath("tests/fake_package/fnord_1.0_source.changes") def test_upload(): """ Test the upload of a package """ path = _build_fnord() upload(path, 'test') def test_double_upload(): """ Test a double-upload (and force block) """ path = _build_fnord() upload(path, 'test') try: upload(path, 'test') assert True is False except UploadException: pass dput-ng-1.7/tests/test_configs.py0000644000000000000000000000200412114414240014015 0ustar from dput.util import validate_object, load_config from dput.exceptions import InvalidConfigError import dput.core import os dput.core.CONFIG_LOCATIONS = { os.path.abspath("./tests/dputng"): 0 } # Isolate. def test_config_load(): """ Make sure we can cleanly load a config """ obj = load_config('profiles', 'test_profile') comp = { "incoming": "/dev/null", "method": "local", "meta": "ubuntu" } for key in comp: assert obj[key] == comp[key] def test_config_validate(): """ Make sure we can validate a good config """ obj = load_config('profiles', 'test_profile') validate_object('config', obj, "profiles/test_profile") def test_config_invalidate(): """ Make sure a bad validation breaks """ obj = load_config('profiles', 'test_profile_bad') try: validate_object('config', obj, "profiles/test_profile_bad") assert True is False except InvalidConfigError as e: assert e.config_name == "profiles/test_profile_bad" dput-ng-1.7/tests/dputng/0000755000000000000000000000000012160717773012303 5ustar dput-ng-1.7/tests/dputng/hooks0000777000000000000000000000000012114414240016035 2../../skel/hooks/ustar dput-ng-1.7/tests/dputng/metas0000777000000000000000000000000012114414240016011 2../../skel/metas/ustar dput-ng-1.7/tests/dputng/profiles/0000755000000000000000000000000012241260762014115 5ustar dput-ng-1.7/tests/dputng/profiles/test_profile.json0000644000000000000000000000033512241260742017506 0ustar { "incoming": "/dev/null", "meta": "ubuntu", "method": "local", "supported-distribution": { "allowed": [ "release" ], "known": [ "release" ] } } dput-ng-1.7/tests/dputng/profiles/test_profile_bad.json0000644000000000000000000000026012241260736020314 0ustar { "incoming": "/dev/null", "supported-distribution": { "allowed": [ "release" ], "known": [ "release" ] } } dput-ng-1.7/tests/dputng/profiles/DEFAULT.json0000777000000000000000000000000012114414240023461 2../../../skel/profiles/DEFAULT.jsonustar dput-ng-1.7/tests/dputng/profiles/test.json0000644000000000000000000000040212241260762015763 0ustar { "-hooks": [ "gpg" ], "incoming": "/dev/null", "meta": "ubuntu", "method": "local", "supported-distribution": { "allowed": [ "release" ], "known": [ "release" ] } } dput-ng-1.7/tests/dputng/codenames0000777000000000000000000000000012160717773017505 2../../skel/codenames/ustar dput-ng-1.7/tests/dputng/interfaces0000777000000000000000000000000012114414240020035 2../../skel/interfaces/ustar dput-ng-1.7/tests/dputng/schemas0000777000000000000000000000000012114414240016635 2../../skel/schemas/ustar dput-ng-1.7/tests/dputng/uploaders0000777000000000000000000000000012114414240017563 2../../skel/uploaders/ustar dput-ng-1.7/docs/0000755000000000000000000000000012231260475010557 5ustar dput-ng-1.7/docs/_static/0000755000000000000000000000000012114414240012174 5ustar dput-ng-1.7/docs/_static/.PLACEHOLDER0000644000000000000000000000000012114414240013705 0ustar dput-ng-1.7/docs/man/0000755000000000000000000000000012240211077011324 5ustar dput-ng-1.7/docs/man/dcut.1.man0000644000000000000000000001714312240211077013125 0ustar DCUT(1) ======= :doctype: manpage NAME ---- dcut - Debian archive command file upload tool SYNOPSIS -------- *dcut* [-h] [-d] [-c 'FILE'] [-m 'MAINTAINER'] [-k 'KEYID'] [-O 'FILENAME'] [-P] [-s] [-U 'FILENAME'] [-i 'FILENAME'] [-v] ['HOST'] SUBCOMMAND ... *dcut* ['OPTIONS'] ['HOST'] SUBCOMMAND ['SUB-COMMAND OPTIONS'] DESCRIPTION ----------- *dcut* can create and/or upload command files understood by the debian archive kit ("dak") software. It provides an extensible interface so that third party authors can easily integrate more sub-commands. Hence, your running instance of *dcut* may understand more commands than these documented here. Refer to the respective documentations for these commands. Similar to *dput*, a 'HOST' can be specified as a target site for the command file. Likewise, the same default host selection criteria apply. It also parses the same configuration files described there. It should be noted that this does not support the same interface as the old dput binary. Please be sure to double-check scripts that depend on old-style dput's interface. OPTIONS ------- *-c, --config*=FILE:: Configuration file to parse. *-d, --debug*:: Enable debug messages. Repeat twice to increase the verbosity level. *-f, --force*:: Bypass all sanity checks and upload the commands file as is. Use with caution. *-m, --maintainer*=MAINTAINER:: Use 'MAINTAINER' for the uploader field and GnuPG key selection. Provide it as a full identity, that is in "+J Uploader +" format. This has no effect when the *upload* command is used. *-k, --keyid*=KEYID:: Use 'KEYID' as a key for signing. Default is to use DEBEMAIL and DEBFULLNAME, or whatever identity was provided with *--maintainer*. This has no effect when the *upload* command is used. *-O, --output*=FILENAME:: Write the resulting commands file to 'FILENAME' instead of uploading it. This option should not be used with the *upload* command. The selected 'FILENAME' wont be overwritten if it exists already. This is very helpful for testing that things work correctly. *-P, --passive*:: Force FTP passive mode when uploading the package through FTP. This option is deprecated - use profiles instead. *-s, --simulate*:: Simulate the upload only. This runs all pre-upload checks, initializes the upload handler but does not actually store any file. *-i, --input*=FILENAME:: Ignored silently for compatibility with old-style dput command lines. *-v, --version*:: Print version information and exit *HOST*:: Target host to upload a package. It has the same behavior and semantics as *dput(1)*'s 'HOST' argument. It also uses the same profiles from it. SUB-COMMANDS ------------ Sub-commands are actually implementing commands understood by be archive software. They can take individual arguments which must appear after specifying the actual desired sub-command. cancel ~~~~~~ Cancel an upload entirely. The upload is referred to as a changes file name existing *remote* in the incoming or deferred queues. OPTIONS ^^^^^^^ Takes one argument *-f*, *--filename*='FILENAME':: The changes file name which refers to the upload to be cancelled. rm ~~ Remove a lost or wrongly uploaded file from the incoming directory. The argument is interpreted as a path fragment by the archive software. Therefore, to delete a filename from a deferred queue, refer to it by using the full path For example, to delete a broken upload in the DELAYED queue, use the command dcut rm -f DELAYED/X-day/foobar.deb Alternatively, the '--searchdirs' argument instructs the archive software to search for a file name in all directory. Hence, this command is equivalent to the command before: dcut rm --searchdirs -f foobar.deb OPTIONS ^^^^^^^ Takes at least one argument *-f*, *--filename*='FILENAME':: The file name to be removed. This argument can be repeated, and also knows about the shell wildcards *, ?, and []. However, please keep your local shell replacements in mind when supplying shell meta characters. You may need to escape them or provide them within quotes. *--searchdirs*:: Search in all directories for the given file. Only supported for files in the DELAYED queue. dm ~~ Manage Debian Maintainer (DM) upload permissions. Debian Developers can grant or revoke them package upload permissions using this command. Takes the Debian Maintainer and the action to perform as argument. Note, dcut will not perform any validation for conflicting arguments within *--allow* and *--deny* below. This handling is left to the archive software, which is currently processing *--allow* before *--deny* as an implementation detail. OPTIONS ^^^^^^^ *--uid*:: Any searchable, unique identity to identify an existing Debian Maintainer. This can be a (full) name an e-mail address or a GnuPG fingerprint of any existing Debian Maintainer. Note, the identity provided must be known in the DM keyring installed on your local system. The keyring is used to validate the supplied argument and makes sure the identity hint supplied matches *exactly* one DM. If the user you want to change ACLs on is not known to the local DM keyring, you can provide the *full* GPG user ID as argument, and pass --force, to cause dcut to bypass any argument checking/translation. Please note, this will generate a commands file which will be uploaded literally as is. Use with caution. *--allow*=PACKAGES:: Source package(s) where permissions to upload should be granted. Repeat the argument to apply permissions to more than one package at once. *--deny*=PACKAGES:: Source package(s) where permissions to upload should be denied. Repeat the argument to apply permissions to more than one package at once. reschedule ~~~~~~~~~~ Reschedule an upload. This command can move a deferred upload to any other deferred queue. OPTIONS ^^^^^^^ Takes two arguments *-f*, *--filename*='FILENAME':: file name to be rescheduled *-d*, *--days*='DAYS':: Reschedule the upload to DAYS days. Takes a numeric argument from 0 to 15 corresponding to the respective delayed queues. Note, 0-day is not the same as uploading to incoming straight. upload ~~~~~~ This is a pseudo-command (that is, it is handled within dcut and not forwarded to the archive kit) which uploads a locally existing commands file as is. However, no checks are performed for this file. Use with caution. OPTIONS ^^^^^^^ Takes one argument *-f*, *--filename*='FILENAME':: A local file name which is uploaded as is to the archive software. EXIT STATUS ----------- *0*:: Success *1*:: A runtime check returned an error *2*:: An internal error was detected, for example while loading configuration files *3*:: An upload error was detected, for example a permission or authentication problem while uploading files BUGS ---- Report bugs to EXAMPLES -------- $ dcut dm --uid "Paul Tagliamonte" --allow glibc $ dcut dm --uid 0x0DEFACED --allow linux --deny kfreebsd9 $ dcut dm --uid paul@example.com --allow eglibc linux $ dcut rm --searchdirs -f udj-desktop-client_0.5.1-2_amd64.deb $ dcut ftp-master rm -f 'linux*.deb' AUTHOR ------ dput-ng was originally written by `Arno Töll ` and `Paul Richard I by the Grace of God of the United Kingdom of Debian and Ubuntu and of his other realms and territories King Head of the Fluxbox Window Manager Defender of the Faith Tagliamonte `. RESOURCES --------- *dput(5)*, *dput.cf(5)*, *dcut(1)*, *gpg(1)*, *dirt(1)* COPYING ------- Copyright (C) 2012 dput-ng authors. Free use of this software is granted under the terms of the GNU General Public License (GPL) Version 2 or later. dput-ng-1.7/docs/man/dput.1.man0000644000000000000000000001552212160724715013152 0ustar DPUT(1) ======= :doctype: manpage NAME ---- dput - Debian package upload tool SYNOPSIS -------- *dput* [-h] [-d] [-c FILE] [-D] [-e DAYS] [-F] [-f] [-l] [-U] [-o] [-O OVERRIDE] [-S UNSET] [-P] [-s] [-u] [-v] [-V] [HOST] CHANGES-FILE [CHANGES-FILE ...] DESCRIPTION ----------- dput-ng is a Debian package upload tool which provides an easy to use interface to Debian (like) package archive hosting facilities. It allows anyone who works with Debian packages to upload their work to a remote service, including Debian's ftp-master, mentors.debian.net, Launchpad or other package hosting facilities for Debian package maintainers. 'HOST' may optionally specify a target stanza from any configured configured profile which is selected as a upload target. If 'HOST' is omitted a default host using the heuristic described below is selected. The only mandatory argument is a 'CHANGES-FILE' which is interpreted as a Debian +package.changes+ file denoting the desired package to be uploaded. Packages being uploaded already, but not processed yet can be removed later from the destination using *dcut*. OPTIONS ------- *-c, --config*=FILE:: Configuration file to parse. This option will override all other configuration files. *-d, --debug*:: Enable debug messages. Repeat twice to increase the verbosity level. *-D, --dinstall*:: Ignored silently for compatibility with old-style dput command lines. Please use a post-upload hook or similar to reproduce this functionality. *-e, --delayed*=DAYS:: Upload to the delayed queue, instead of the usual incoming directory. This option takes an argument from 0 to 15 corresponding to the respective DELAYED queue. *-F, --full-upload-log*' Write more verbose .upload logs. When set to, upload logs will include more details. This setting overrides profile defaults when provided. *-f, --force*:: Force an upload, even if the upload log exists already. *-l, --lintian*:: Run Lintian before uploading the package. Note, this option is deprecated. Profile targets have the ability to properly handle invoking lintian before pushing it to the remote host, such as the *run_lintian* key. This option is a shortcut for --override "run_lintian=true" *-o, --check-only*:: Only run pre-upload checks for the package, do not actually upload. This is useful to help in testing new checks, or verifying the profile will work as expected. *-O, --override*=OVERRIDE:: Override profile key. This option takes the highest precedence and replaces any profile setting. Use this switch if you want to change a configured profile value without changing the profile itself. This option accepts any key which can be configured in a profile (see *dput(5)*). Sub keys are addressed using a dot notation. Keys are separated using a equals sign ("="). For example, to override the 'allow_dcut' you may do: --override "allow_dcut=true" The '--override' option may be repeated: --override "check-debs.enforce=debs" --override "check-debs.skip=false" Providing the same key on the command line several times will be additive. Un-setting a key entirely can be achieved using the '-S' option. Such overrides take no argument. Thus, the command line --override "allow_dcut=true" --unset "run_lintian" will set 'allow_dcut' to 'TRUE' and revert the 'run_lintian' key to whatever the internal default value is set. *-P, --passive*:: Force FTP passive mode when uploading the package through FTP. This option is deprecated - please declare this in the target profile. This option is a shortcut for --override "passive_ftp=true" *-s, --simulate*:: Simulate the upload only. This runs all pre-upload checks, initializes the upload handler but does not actually push any files to the remote host. This argument can be repeated twice in which case also the network connection is set-up (for example logging in through the FTP or SFTP protocol) and tested for its functionality. *-S,--unset*=OVERRIDE:: Override the configured profile key by unsetting its value. See *-O* for a full explanation of the behavior. *-U, --no-upload-log*:: Do not write a .upload log file after uploading. *-u, --unchecked*:: Do not check GnuPG signature. You may also set this in your profile with the *allow_unsigned_uploads* key. *-v, --version*:: Ignored silently for compatibility with old-style dput command lines. *-V, --check-version*:: Ignored silently for compatibility with old-style dput command lines. *HOST*:: Target host to upload a package. This refers to any existing upload site, which either can be a section in old-style *dput.cf* files, or alternatively any profile in a read *dput.d* directory. If *HOST* was not supplied, the (first) profile having the +default_host_main+ flag set to a non-empty string will be selected. If neither, 'HOST' was present, nor any profile setting the upload host, dput-ng scans for a profile called 'ftp-master' which will be used on a successful look-up. *CHANGES-FILE*:: A Debian +package.changes+ file. Arguments may be repeated several times to upload more than one package at once. PROFILES -------- Profiles may define upload 'HOST' names, log-in details and their required upload methods used to upload packages. Moreover, profiles define checks which are running before and after uploading. Their format is described in *dput(5)*. Additionally, *dput* reads old-style configuration files from INI style configuration files. This format is deprecated for use in dput-ng and described in *dput.cf(5)*. In particular this configuration file format does not support all configurable settings which are specific to dput-ng. It is possible to run *dput* with any combination of new-style and old-style configuration files. When both types of files are present, EXIT STATUS ----------- *0*:: Success *1*:: A runtime check returned an error *2*:: An internal error was detected, for example while loading configuration files *3*:: An upload error was detected, for example a permission or authentication problem while uploading files BUGS ---- Report bugs to EXAMPLES -------- $ dput ppa:paultag/fluxbox fluxbox_1.3.2-4~ppa1_source.changes $ dput ftp-master fbautostart_2.718281828-1_amd64.changes $ dput dput-ng_0.9.5_amd64.changes $ dput -d -d localhost node-jslint_0.1.8-1~wicked1_amd64.changes AUTHOR ------ dput-ng was originally written by `Arno Töll ` and `Paul Richard I by the Grace of God of the United Kingdom of Debian and Ubuntu and of his other realms and territories King Head of the Fluxbox Window Manager Defender of the Faith Tagliamonte `. RESOURCES --------- *dput(5)*, *dput.cf(5)*, *dcut(1)*, *dirt(1)* COPYING ------- Copyright (C) 2012 dput-ng authors. Free use of this software is granted under the terms of the GNU General Public License (GPL) Version 2 or later. dput-ng-1.7/docs/man/dput.cf.5.man0000644000000000000000000000713512114414240013532 0ustar DPUT.CF(5) ========= :doctype: manpage NAME ---- dput.cf - Debian package upload tool configuration file DESCRIPTION ----------- This manpage gives a brief overview of dput's configuration file and the available options in it. dput is a tool to upload Debian packages to the archive. FILES AND FORMAT ---------------- *dput* reads, in order, these files: 1. '/etc/dput.cf' 2. '~/.dput.cf' 3. The file supplied via command line argument By default *all* configuration file locations are parsed, and overlaid in a additive manner. This way both, entire profiles and actual settings within a profile is inherited from any parent location defining a key within the current scope. dput.cf consists of different groups of configuration options, one for each host where you want to be able to upload packages. Hosts are defined using an identifier header with a short name for the host, enclosed in square brackets. Note that only if multiple such headers are encountered in the configuration, only the group following the last header is considered. This is done to avoid confusion when overriding a global configuration file with a user-specific one. There's a special identifier, [DEFAULT], which holds default parameters for all the hosts. The defaults can be overridden by redefining them again in each host section. SUPPORTED KEYS ~~~~~~~~~~~~~~ These keys are supported in dput.cf files. For their meaning refer to *dput(5)*. *allow_dcut*, *allow_unsigned_uploads*, *allowed_distributions*, *default_host_main*, *default_keyid*, *fqdn*, *distributions*, *hash*, *incoming*, *method*, *login*, *passive_ftp*, *post_upload_command*, *pre_upload_command*, *run_lintian*. UNSUPPORTED LEGACY KEYS ~~~~~~~~~~~~~~~~~~~~~~~~ These keys known and understood to the original dput implementation are not understood by dput-ng: *method*:: This command is mostly supported. However, there is no support for the 'rsync' method. *delayed*:: This key is not supported. Use the '*--delayed*' switch from the command line, or set *incoming* to the actual delayed directory if you really rely on this key. *run_dinstall*:: Not supported. Please submit a post-upload hook if you'd like to run *dinstall*. *check_version*:: Not supported. *progress_indicator*:: Not supported yet. *scp_compress*:: SCP compression is enabled by default. This is, however not configurable currently. *ssh_config_options*:: Not supported. Please switch to the 'sftp' method which supports your options from *ssh_config(5)* natively. UNSUPPORTED KEYS ~~~~~~~~~~~~~~~~ These keys are not supported in old-style configuration files. However, their values are inherited from their new-style defaults. See *dput(5)* for details: *default_keyid*, *hooks*, *interface*, *meta*, *full_upload_log*. BUGS ----- Please send bug reports to the author. FILES ----- '/etc/dput.cf':: global dput configuration file '~/.dput.cf':: peruser dput configuration file AUTHOR ------ dput was originally written by Christian Kurz. Updated by Thomas Viehmann . dput-ng does not re-use notable portions of code, but was heavily inspired by its behavior. Much appreciated. dput-ng was originally written by `Arno Töll ` and `Paul Richard I by the Grace of God of the United Kingdom of Debian and Ubuntu and of his other realms and territories King Head of the Fluxbox Window Manager Defender of the Faith Tagliamonte `. SEE ALSO -------- *dput(1)*, *dput(5)* COPYING ------- Copyright (C) 2012 dput-ng authors. Free use of this software is granted under the terms of the GNU General Public License (GPL) Version 2 or later. dput-ng-1.7/docs/man/dirt.1.man0000644000000000000000000000335412114414240013124 0ustar DIRT(1) ======= :doctype: manpage NAME ---- dirt - dput information retrieval tool SYNOPSIS -------- *dirt* [-h] {hosts,blame,list,info} ... DESCRIPTION ----------- *dirt* is a data retrieval and change tool, being able to retrieve meta information about *dput*(1) configuration files and hooks. This command is not completed yet. Interface and behavior changes are expected in future releases. OPTIONS ------- SUB COMMANDS ~~~~~~~~~~~~ *hosts*:: print the lists of hosts that dput knows about *blame*:: get information on where dput is finding keys *list*:: list all the hooks registered to dput *info*:: get some help on a hook EXIT STATUS ----------- *0*:: Success *1*:: A runtime check returned an error *2*:: An internal error was detected, for example while loading configuration files *3*:: An upload error was detected, for example a permission or authentication problem while uploading files BUGS ---- Report bugs to EXAMPLES -------- $ dirt list $ dirt blame -p ftp-master $ dirt info --hook source AUTHOR ------ dput-ng was originally written by `Arno Töll ` and `Paul Richard I by the Grace of God of the United Kingdom of Debian and Ubuntu and of his other realms and territories King Head of the Fluxbox Window Manager Defender of the Faith Tagliamonte `. BUGS ---- This command is not completed yet. Interface and behavior changes are expected in future releases. RESOURCES --------- *dput(5)*, *dput.cf(5)*, *dcut(1)* COPYING ------- Copyright (C) 2012 dput-ng authors. Free use of this software is granted under the terms of the GNU General Public License (GPL) Version 2 or later. dput-ng-1.7/docs/man/dput.5.man0000644000000000000000000003313112160717773013160 0ustar DPUT(5) ======= :doctype: manpage NAME ---- dput - configuration file format for dput-ng DESCRIPTION ----------- *dput* supports two configuration file formats. The old-style configuration format was originally introduced by dput and is described in *dput.cf(5)*. This manpage describes new-style configuration files only. All details are covered in in which is available in the dput-ng-doc package. FILES AND FORMAT ---------------- Upload targets are configured using JSON as described in *RFC 4627*. In a nutshell, *dput* configuration allows insignificant whitespace before or after any type statement. Each upload profile is stored in its own file and is represented as a pair of curly brackets surrounding name/value pairs described below. Both, name and values are strings. A single colon separates the name from the value. A string begins and ends with quotation marks and may be escaped. Booleans are either 'true' or 'false' (mind these are not surrounded by quotation marks). Some keys names are accepting lists as possible value. A list is represented as square brackets surrounding zero or more values, separated by commas. *dput* reads, in order, these directories: 1. '/usr/share/dput-ng/' 2. '/etc/dput.d/' 3. '~/.dput.d/' 4. The directory supplied via command line argument Moreover, old-style configuration files are parsed and read. See *READING TRADITIONAL CONFIGURATION FILES* below. In general, packages are installing pre-defined defaults for popular upload targets to '/usr/share/dput-ng/profiles/'. System administrators who wish to override or create a new system-wide and shared target for many users may choose '/etc/dput.d/profiles'. Finally, local targets may be written to '~/.dput.d/profiles' for personal upload targets. Within each configuration directory, there may be another tier of configuration directories. There, these actual configuration directories may exist: * *metas/* define super-classes of upload profiles. They can define any name and value known to profiles (see below) which are shared across profiles. * *profiles/* define upload profiles. Files therein are looked-up by their name as 'HOST' argument by *dput*. This is, where upload hosts are defined. Moreover, these directories can exist and are documented here for the sake of completeness. However, users typically do not need to touch these unless you are developing or customizing existing plug-ins to *dput* or *dcut* * *hooks/* define entry hooks to checker functions which are registered for use with *dput*. See *HOOKS* below. * *commands/* define entry hooks to command functions which are registered for use with *dcut*. * *interfaces/* define entry hooks to user interface functions which are registered for use with *dput* and *dcut*. They are responsible to retrieve data from the user. By default *all* configuration file locations are parsed, and overlaid in a additive manner. This way both, entire profiles and actual settings within a profile is inherited from any parent location defining a key within the current scope. Details are explained in the *INHERITANCE OF KEYS* section. PROFILES --------- Profiles are indexed as '.json' within the 'profiles/' configuration directory. Every profile may define these keys. Optionally a profile called 'DEFAULT.json' can be defined as a superset of all existing profiles. Any other profile will inherit values from this profile. For a finer grained control see *meta* keyword and *META-CLASSES* below. Following is an example configuration for a local upload profile, named "'localhost.json'". { "+hooks": [ "lintian" ], "-hooks": [ "gpg" ], "incoming": "~/incoming", "meta": "debian", "method": "local", "run_lintian": true } Supported keys are: *allow_dcut* (boolean):: This defines if you are allowed to upload a dcut changes file to the queue to remove or move files. See *dcut(1)*. *allow_unsigned_uploads* (boolean):: This defines if you are allowed to upload files without a GnuPG signature to this host or not. *allowed_distributions* (string):: A regular expression (of Python re module syntax) that the distribution field must match or dput will refuse the upload. *default_host_main* (string):: This defines the default host for packages that are allowed to be uploaded to the main archive. This variable is used when guessing the host to upload to. *default_keyid* (string):: This defines the default GPG key ID to be used to sign dcut commands. This option can be overridden by -k parameter. *full_upload_log* (boolean):: This defines the verbosity of upload logs. When set to *true*, logs will include more details. This setting might be overridden on the command line and defaults to *false*. *fqdn* (string):: This is the fully qualified domain name that *dput* will connect to as a target site. *distributions* (string):: This defines a comma-separated list of distributions that this host accepts, used to guess the host to use when none is given on the command line. *hash* (string):: The hash algorithm that should be used in calculating the checksum of the files before uploading them. Currently, dput accepts the following values for hash: * 'sha1': Perform validation of the SHA-1 hash (default when omitted) * 'sha256': Perform validation of the SHA-256 hash * 'md5': Perform validation of the MD5 hash *hooks* (list of string):: Defines a list of checkers which are running before or after the upload. See *HOOKS* below. *interface* (string):: Not supported yet. This is a known limitation. *incoming* (string):: The directory that *dput* should upload files to. Most upload sites do not allow to write files in the log-in directory. Specify a path here, to which *dput* should change the directory to, before starting to write files. *method* (string):: Use the specified method to upload your package. Currently these alternatives are supported: * 'ftp':: Use FTP to upload files * 'http' or 'https':: Use HTTP or HTTPS to upload files * 'local':: Upload to a locally mounted location of the file system. Internally this calls *install(1)*. * 'scp':: Use scp to upload files. *This method is deprecated*, use 'sftp' instead when possible. * 'sftp':: Use the sftp protocol (a secured file transfer via SSH). * *dput-ng* does not support 'rsync'. *login* (string):: Your login on the machine named before. A single asterisk ('*') will cause the 'scp', 'sftp' and uploaders to not supply a login name when calling trying to authenticate. *meta* (list of string):: Specify a list of super classes from which the profile should inherit settings explicitly. This is different to the 'DEFAULT.json' profile in such that this defines settings conditionally, and not for all profiles. *passive_ftp* (boolean):: This option defines whether *dput* should use passive or active FTP for uploading a package to one of the upload queues. This name is only meaningful when 'method' is set to 'ftp'. *post_upload_command* (string):: This option defines a command to be run by dput after a successful upload. The command is invoked via the shell and does not get passed any argument. See *PROCESSORS* for more sophisticated approaches which are integrated in the upload process. *pre_upload_command* (string):: This option defines a command to be run by dput before an upload. The command is invoked via the shell and does not get passed any argument. See *HOOKS* for more sophisticated approaches which are integrated in the upload process and can gracefully interrupt the upload process. *run_lintian* (boolean):: This option defines if lintian should be run before the package will be uploaded or not. *This setting is deprecated* but works as a fallback to the corresponding *HOOK*. The Lintian hook allows much more fine grained control over the Lintian invocation. READING TRADITIONAL CONFIGURATION FILES --------------------------------------- As outlined initially, *dput* reads and parses traditional INI style configuration files. It's format is documented in *dput.cf(5)*. These files are deprecated, but for the time being read and parsed. We encourage the removal of these global and local configuration files entirely. Having that said, when in place old-style configuration files will overrule new style files, to preserve a possibly modified legacy behavior. That means, in order configuration values are inherited and keys are successively overwritten in this order: 1. '/etc/dput.d/profiles' 2. '/etc/dput.cf' 3. '~/.dput.d/profiles' 4. '~/.dput.cf' This means, old-style configuration files always take relative precedence when installed. Use them with caution. INHERITANCE OF KEYS ------------------- By default, keys will override any previously defined value. However, as a special case, there are three operators (*=*, *+* and *-*) that may be prefixed to names to merge with existing inherited values. This is beneficial when a profile wishes to add or remove values from an existing key which accepts lists of values. This is mostly useful to *hooks* which may want to extend an existing profile key that is inheriting values via it's meta-class or parent. * The *=* operator is the default operator when no operator was explicitly provided. It overwrites any previous key. * The *+* operator is additive. When set, it merges the supplied value(s) with any previous value * The *-* operator is subtractive. When set, it removes the supplied value(s) from any previous value. The DEFAULT Profile ~~~~~~~~~~~~~~~~~~~ There is a special profile called 'DEFAULT' ("'DEFAULT.json'" in any configuration location). This profile is the root profile. All profiles are automatically inheriting values from this profile. Values set there are global defaults. The profile itself is subject to the same inheritance rules as any other profile itself as well. All keys and values can be set in the 'DEFAULT' profile. META PROFILES ~~~~~~~~~~~~~ Configuration files may declare an optional *meta* key, who's value is the name of a meta-configuration to be placed under this configuration. You can check for meta-configuration in '/usr/share/dput-ng/metas', '/etc/dput.d/metas' or '~/.dput.d/metas'. This helps declare common settings (such as general Debian archive configuration values (GPG requirements, enforcing that binary files exist, etc) without having to maintain may of the same values in many places). They are different to the 'DEFAULT' profile in such, that no profile automatically inherits values from a meta profile, but only upon explicit request. Meta profiles can in turn inherit itself values from other meta profiles. OVERRIDING SINGLE VALUES ~~~~~~~~~~~~~~~~~~~~~~~~ Here's an example stanza from a local dput config to remove an annoying hook from being run: '~/.dput.d/profiles/DEFAULT.json': { "hooks": [ "gpg", "lintian" ] } At this point, every profile will invoke the hooks 'gpg', 'lintian' '~/.dput.d/metas/my-defaults.json': { "hooks": [ "checksum", ] } This defines a meta profile named 'my-defaults', which will also invoke the 'checksum' hook. At this point this meta profile, inherits values from 'DEFAULT' and therefore invokes the hooks 'gpg', 'lintian' and 'checksum'. '~/.dput.d/profiles/ftp-master.json': { "-hooks": [ "lintian" ] "meta": [ "my-defaults" ] } At this point, the profile 'ftp-master' will inherit values from 'DEFAULT' and 'my-defaults'. However, the '-' operator prefix removes 'lintian' from the checker list. Hence, 'ftp-master' will run the checkers 'gpg' and 'checksum'. HOOKS ------ Hooks are pre- or post-uupload checks, They are pluggable components running before or after the upload of a package. Whether they run before or after the upload is determined by the JSON definition of a hook. That is an implementation detail the user typically does not need to worry about. Pre-Upload Hooks ~~~~~~~~~~~~~~~~ Pre-upload hooks are pluggable components which are designed to run before the upload actually happens. This typically involves consistency checks, sanity checks and similar tasks. The list of available pre-upload hooks can be obtained using *dirt(1)*. The hooks invoked by default are determined on a per-profile basis by retrieving the setting of the *hooks* key. Hooks also run in simulation and check-only mode. It is easy to write your own hook extensions. Consult the manual for instructions. Post-Upload Hooks ~~~~~~~~~~~~~~~~~ Processors are pluggable components which are designed to run after the upload actually happens. They cannot interrupt the upload, because they are invoked after a successful upload only. They do _not_ run when *dput* was invoked with check-only or simulation mode. Such post-upload hooks may invoke post- processing tasks such as closing or filing bugs. The list of available processors can be obtained using *dirt(1)*. The hooks invoked by default are determined on a per-profile basis by retrieving the setting of the *hooks* key and follow the same rules as pre-upload hooks. It is easy to write your own hook extensions. Consult the manual for instructions. FILES ----- /usr/share/dput-ng/ /etc/dput.d/ ~/.dput.d/ AUTHOR ------ dput-ng was originally written by `Arno Töll ` and `Paul Richard I by the Grace of God of the United Kingdom of Debian and Ubuntu and of his other realms and territories King Head of the Fluxbox Window Manager Defender of the Faith Tagliamonte `. RESOURCES --------- *RFC 4627*, */usr/share/doc/dput-ng/html/reference/*, *dput(1)*, *dcut(1)*, *dcut(1)* dput-ng-1.7/docs/library/0000755000000000000000000000000012114414240012212 5ustar dput-ng-1.7/docs/library/hooks/0000755000000000000000000000000012160717773013357 5ustar dput-ng-1.7/docs/library/hooks/deb.rst0000644000000000000000000000013012160717773014635 0ustar Debian file routines ==================== .. automodule:: dput.hooks.deb :members: dput-ng-1.7/docs/library/hooks/impatient.rst0000644000000000000000000000011012160717773016073 0ustar Impatient ========= .. automodule:: dput.hooks.impatient :members: dput-ng-1.7/docs/library/hooks/checksum.rst0000644000000000000000000000010712160717773015711 0ustar Checksums ========= .. automodule:: dput.hooks.checksum :members: dput-ng-1.7/docs/library/hooks/index.rst0000644000000000000000000000031312160717773015215 0ustar Hook Implementations ==================== Documentation Index ------------------- Contents: .. toctree:: :maxdepth: 2 lintian archive checksum deb distribution gpg impatient dput-ng-1.7/docs/library/hooks/archive.rst0000644000000000000000000000010212160717773015523 0ustar Archive ======= .. automodule:: dput.hooks.archive :members: dput-ng-1.7/docs/library/hooks/distribution.rst0000644000000000000000000000013312160717773016625 0ustar Distribution bits ================= .. automodule:: dput.hooks.distribution :members: dput-ng-1.7/docs/library/hooks/lintian.rst0000644000000000000000000000016012114414240015522 0ustar Lintian Checker Implementation ============================== .. automodule:: dput.hooks.lintian :members: dput-ng-1.7/docs/library/hooks/gpg.rst0000644000000000000000000000006612160717773014670 0ustar GPG === .. automodule:: dput.hooks.gpg :members: dput-ng-1.7/docs/library/profile.rst0000644000000000000000000000120312114414240014400 0ustar Upload Target / Profile Implementation ====================================== This contains a lot of backing code to get at profiles. Commonly used functions ----------------------- .. autofunction:: dput.profile.load_profile .. autofunction:: dput.profile.profiles Multi Configuration Implementation ---------------------------------- .. warning:: This is mostly just used internally, please don't use this directly unless you know what you're doing(tm). In most cases, :func:`dput.profile.load_profile` and :func:`dput.profile.profiles` will do the trick. .. autoclass:: dput.profile.MultiConfig :members: :private-members: dput-ng-1.7/docs/library/uploader.rst0000644000000000000000000000013412114414240014555 0ustar Uploader Implementation ======================= .. automodule:: dput.uploader :members: dput-ng-1.7/docs/library/interface.rst0000644000000000000000000000015112114414240014701 0ustar User Interface Implementation ============================= .. automodule:: dput.interface :members: dput-ng-1.7/docs/library/interfaces/0000755000000000000000000000000012114414240014335 5ustar dput-ng-1.7/docs/library/interfaces/index.rst0000644000000000000000000000024312114414240016175 0ustar User Interfaces Implementations =============================== Documentation Index ------------------- Contents: .. toctree:: :maxdepth: 2 clinterface dput-ng-1.7/docs/library/interfaces/clinterface.rst0000644000000000000000000000015112114414240017343 0ustar CLInterface Implementation ========================== .. automodule:: dput.interfaces.cli :members: dput-ng-1.7/docs/library/index.rst0000644000000000000000000000077012114414240014057 0ustar Internal Documentation ====================== The documentation here is to document the internal implementation of dput, for use by new contributors, old contributors, bus-factor reasons, checker hackers, and interested persons. Documentation Index ------------------- Top-level modules: .. toctree:: :maxdepth: 3 core util changes profile exceptions overrides hooks hooks/index interface interfaces/index uploader uploaders/index config configs/index dput-ng-1.7/docs/library/hooks.rst0000644000000000000000000000012512114414240014065 0ustar Hook Implementation ====================== .. automodule:: dput.hooks :members: dput-ng-1.7/docs/library/util.rst0000644000000000000000000000124512114414240013723 0ustar Misc. Utilities =============== This module contains functions that don't have a rightful home elsewhere, or are of general use. Configuration Access -------------------- These functions are used to read a config off the filesystem, for use elsewhere in dput. .. autofunction:: dput.util.get_configs .. autofunction:: dput.util.load_config Object Loaders -------------- These functions aid in loading a defined, dynamically imported "plugin". .. autofunction:: dput.util.get_obj .. autofunction:: dput.util.load_obj Invocation ---------- These functions aid in running things. .. autofunction:: dput.util.run_command .. autofunction:: dput.util.run_func_by_name dput-ng-1.7/docs/library/changes.rst0000644000000000000000000000053612114414240014360 0ustar Changes File Implementation =========================== This module contains code to aid in processing .changes files. Most of this code has been yanked from Jonny Lamb. Thanks, Jonny. Facade ------ .. autofunction:: dput.changes.parse_changes_file Abstraction ----------- .. autoclass:: dput.changes.Changes :members: :private-members: dput-ng-1.7/docs/library/overrides.rst0000644000000000000000000000011212114414240014740 0ustar Override Implementation ======================= .. XXX: Arno: Finish me. dput-ng-1.7/docs/library/config.rst0000644000000000000000000000014412114414240014210 0ustar Configuration Implementation ============================ .. automodule:: dput.config :members: dput-ng-1.7/docs/library/exceptions.rst0000644000000000000000000000131712114414240015127 0ustar Exceptions & Errors =================== This module contains oodles of exceptions that might be thrown or subclassed elsewhere. Base Exceptions --------------- .. autoexception:: dput.exceptions.DputError .. autoexception:: dput.exceptions.UploadException .. autoexception:: dput.exceptions.HookException Configuration File Errors ------------------------- .. autoexception:: dput.exceptions.DputConfigurationError .. autoexception:: dput.exceptions.NoSuchConfigError .. autoexception:: dput.exceptions.InvalidConfigError .. autoexception:: dput.exceptions.NoSuchConfigError Misc Errors ----------- .. autoexception:: dput.exceptions.ChangesFileException .. autoexception:: dput.exceptions.NoSuchHostError dput-ng-1.7/docs/library/core.rst0000644000000000000000000000371012114414240013675 0ustar Object Core =========== The *core* is a place where all the central objects live. This is used so that all the different modules in dput can access common constants. This helps us fake data (great for testing), and maintain sanity. The Logger ---------- Printing to the screen using :func:`print` is wrong, m'kay? Please do **not** use it under any conditions. In it's place, we have a central ``logger`` object, to use as all the bits of dput see fit. The logger object is an instantiation of :class:`dput.logger.DputLogger`, so feel free to use any if it's logging methods. In general, don't use ``info`` or above, unless the user *really* needs to know. Most calls should be to ``debug`` or ``trace``. Example usage:: from dput.core import logger logger.debug("Hello, World!") logger.warning("OH MY DEAR GOD") Configuration Objects --------------------- The core contains two config directories, which are used by the config modules (as well as other, more friendly places). All configs are in the form of a dict, the key being the path, and the value being the "weight" of the path. The higher the weight, the less important it is. Example ``dput.core.CONFIG_LOCATIONS``:: { "/usr/share/dput-ng/": 30, "/etc/dput.d/": 20, os.path.expanduser("~/.dput.d"): 10, } :func:`dput.util.load_config` is used to access a config from this list, and handles meta-classes, and other edge cases when loading. Please use :func:`dput.util.load_config` to load config files from these locations. Example ``dput.core.DPUT_CONFIG_LOCATIONS``:: { "/etc/dput.cf": 15, os.path.expanduser("~/.dput.cf"): 5 } Both are merged into a single list, sorted by list, and used by :class:`dput.profile.MultiConfig` to handle loading and access. Schema Directory ---------------- This is the path to search for validictory schemas. By default, this is set to ``/usr/share/dput-ng/schemas``. These are not treated as normal conf-files. dput-ng-1.7/docs/library/configs/0000755000000000000000000000000012114414240013642 5ustar dput-ng-1.7/docs/library/configs/dputng.rst0000644000000000000000000000015312114414240015674 0ustar New-style dput config files =========================== .. automodule:: dput.configs.dputng :members: dput-ng-1.7/docs/library/configs/index.rst0000644000000000000000000000025612114414240015506 0ustar Configuration File Implementations ================================== Documentation Index ------------------- Contents: .. toctree:: :maxdepth: 2 dputcf dputng dput-ng-1.7/docs/library/configs/dputcf.rst0000644000000000000000000000015312114414240015660 0ustar Old-style dput config files =========================== .. automodule:: dput.configs.dputcf :members: dput-ng-1.7/docs/library/uploaders/0000755000000000000000000000000012114414240014210 5ustar dput-ng-1.7/docs/library/uploaders/scp.rst0000644000000000000000000000013012114414240015521 0ustar SCP Implementation ================== .. automodule:: dput.uploaders.scp :members: dput-ng-1.7/docs/library/uploaders/sftp.rst0000644000000000000000000000013312114414240015713 0ustar SFTP Implementation =================== .. automodule:: dput.uploaders.sftp :members: dput-ng-1.7/docs/library/uploaders/index.rst0000644000000000000000000000026712114414240016056 0ustar Upload Method Implementations ============================= Documentation Index ------------------- Contents: .. toctree:: :maxdepth: 2 ftp http local scp sftp dput-ng-1.7/docs/library/uploaders/local.rst0000644000000000000000000000016012114414240016031 0ustar Local Uploader Implementation ============================= .. automodule:: dput.uploaders.local :members: dput-ng-1.7/docs/library/uploaders/ftp.rst0000644000000000000000000000013012114414240015525 0ustar FTP Implementation ================== .. automodule:: dput.uploaders.ftp :members: dput-ng-1.7/docs/library/uploaders/http.rst0000644000000000000000000000013312114414240015716 0ustar HTTP Implementation =================== .. automodule:: dput.uploaders.http :members: dput-ng-1.7/docs/reference/0000755000000000000000000000000012160717773012526 5ustar dput-ng-1.7/docs/reference/index.rst0000644000000000000000000000030412114414240014342 0ustar Reference Documentation ======================= Documentation Index ------------------- Contents: .. toctree:: :maxdepth: 2 migrating configs contributing hookinstall hooks dput-ng-1.7/docs/reference/hooks.rst0000644000000000000000000001176312160717773014413 0ustar Writing Hooks ============= .. note:: Whether a hook runs before or after uploading a package is a matter of the JSON configuration file. Aside, they are identical. Hooks are a fundamental part of dput-ng. Hooks make sure the package you've prepared is actually fit to upload given the target & current profile. In general, one should implement hooks for things that the remote server would ideally check for before accepting a package. Going beyond that is OK, providing you have the user's go-ahead to do so. Remember, this isn't some sort of magical restriction to upload, most remote servers would be happy with almost anything you put there, these are simply to help reduce the time to notice big errors. Theory of Operation ------------------- Pre-upload Hooks are a simple function which is invoked with a few objects to help aid in the checking process & reduce code. Pre-upload hooks will always be run before an upload, and will be given the digested .changes object, the current profile & a way to interface with the user. Pre-upload hooks (at their core) should preform a single check (as simply as it can), and either raise a subclass of :class:`dput.exceptions.HookException` or return normally. Post-upload hooks work likewise. They are just simple hooks as well, that are slightly different to pre-upload hooks. Firstly, register as a hook by placing the plugin def in the ``hooks`` class. In the event of an error, feel free to just bail out. There's not much you can do, and throwing an error is bad form. For now. This is likely to change. How a Hook Is Invoked ------------------------ Throughout this overview, we'll be looking at the :func:`dput.hooks.checksum.validate_checksums` pre-upload hook. It's one of the most simple hooks, and demonstrates the concept very clearly. To start to understand how this all works, let's take a step back and look at how :func:`dput.hook.run_hook` invokes the hook-function. Basically, ``run_hook`` will grab all the strings in the ``hooks`` key of the profile. They are just that -- simply strings. The hook are looked up using :func:`dput.util.get_obj` (which calls :func:`dput.util.load_config` to resolve the .json definition of the hook). All hooks are declared in the ``hooks`` config class, and look something like the following:: { "description": "checksum pre-upload hook", "path": "dput.hooks.checksum.validate_checksums", "pre": true } .. note:: Use ``"pre": true`` or ``"post": true`` respectively to decide whether the hook should run prior or after uploading a package. For more on this file & how it's used, check the other ref-doc on config files: :doc:`configs` Nextly, let's take a look at the ``path`` key. ``path`` is a python-importable path to the function to invoke. Let's take a look at it a bit more closely:: >>> from dput.hooks.checksum import validate_checksums >>> validate_checksums As you can see, we've imported the target, and it is, in fact, the function that we care about. .. XXX: TODO: More better handling of small scripts which should just be put somewhere dput cares about? Now that we're clear on how we got here, let's check back with the implementation of :func:`dput.hooks.checksum.validate_checksums`:: def validate_checksums(changes, profile, interface): We're passed three objects -- the ``changes``, ``profile`` and ``interface``. The ``changes`` object is an instance of :class:`dput.changes.Changes`, pre-loaded with the target of this upload action. ``profile`` is a simple dict, with the current upload profile. ``interface`` is a subclass of :class:`dput.interface.AbstractInterface`, ready to be used to talk to the user, if something comes up. What To Do When You Find an Issue --------------------------------- During runtime, and for any reason the checker sees fit to do so, the hook may abort the upload by raising a subclass of a :class:`dput.exceptions.HookException`. In cases where the user aught to make the decision (lintian errors, etc), please **prompt** the user for what to do, rather then blindly raising the error. Remember, the user can't override a checker's failure except by disabling the checker. Moreover, never prompt for inputs directly. Use the :class:`dput.interface.AbstractInterface` interface to prompt for data in a uniform way. Don't make people disable you. Be nice. Let's take a look at our reference implementation again:: def validate_checksums(changes, profile, interface): try: changes.validate_checksums(check_hash=profile["hash"]) except ChangesFileException as e: raise HashValidationError( "Bad checksums on %s: %s" % (changes.get_filename(), e) ) As you can see, the checker verifies the hashsums, catches any Exceptions thrown by the code it uses, and raises sane error text. The Exception raised (:class:`dput.hooks.checksum.HashValidationError`) is a subclass of the expected :class:`dput.exceptions.HookException`. dput-ng-1.7/docs/reference/contributing.rst0000644000000000000000000000533612114414240015754 0ustar Contributing to dput ==================== Firstly, thanks for reading! It's super cool of you to want to help! Code + patches -------------- This is one of the bigger areas in which hands are needed. Adding new features and refactoring the codebase is a lot of work, and the more people who want to help with such tasks, the better! dput-ng is extremely pythonic, and it aims to be something enjoyable to hack on. We aim to be, at any time, pep8 clean, pyflakes clean, well-tested and fully documented. If you decide to contribute, please stick to the following rules: * Use names that make sense. In general, try to make the import as descriptive as you can. ``from dput.foo import bar_function`` is pretty lame, try something like ``from dput.profile import load_profile``. * When you contribute a fix, please also contribute some tests to verify what you've done. That's fine if you don't use TDD, in fact, most of us here at dput-ng HQ don't. * docstring **all the things**. Use RST for the docstring blocks, there's a good chance it'll show up in the docs. * Please be explicit about licensing. * Please add your name to AUTHORS, on the first commit. * Ask for feedback *early* Documentation ------------- Documentation is another huge effort that's been going on. Working to better document dput is something that's really important. Working on tutorials, reviewing old & outdated docs, or expanding on existing documentation is something that's sorely needed. If you're also technical, documenting the internals is an ongoing effort, so any help there would be amazing. Some rules here, too: * Be sure to write in complete and clear English. * Cross-reference as much as you can. It really helps. * Include lots of examples. * As for feedback as you go along. Also be sure to have a technical person on the dput team review your work for slight errors as you go along. * Be explicit about licensing * Please add yourself to AUTHORS on your first commit. Hooks ------ Hooks are hugely important as well. Writing new hooks is insanely cool, and sharing them back with the dput-ng community & friends is an awesome thing to do on it's own. Some other random guidelines we thought up: * In general, treat your hook as self-contained and independent. * If you feel your checker hook be in the dput main, please ensure it's properly clean, follows the code guidelines above, and finds a nice home somewhere in the dput codebase. Make sure it's below ``dput.hooks``, though. * It must be distributable under the terms of the GPL-2+ license. Permissive licenses such as Expat or BSD-3 should be fine. When in doubt, ask! .. XXX: Link to a tutorial about writing a checker, etc. MORE! dput-ng-1.7/docs/reference/hookinstall.rst0000644000000000000000000001004112114414240015561 0ustar Installing Hooks ================ This guide will cover exactly how dput-ng handles hooks, and the proper way to install, distribute and tool with hooks. Remember, Hooks are your friend! So, what exactly are hooks? --------------------------- Well, it's pretty simple, actually -- a hook is a Python importable function that takes a few arguments, which are populated with internal dput-ng objects. These objects contain such things as a way to talk to the user, the processed .changes file, and the upload target. Hooks can run either before or after an upload, and hooks that run before the upload may halt an upload by raising a :class:`dput.exceptions.HookException` (or a subclass of that). Alright, you mentioned Python-importable, what exactly does that mean? ---------------------------------------------------------------------- The path given is a fully qualified path to the hook. Here's an example:: >>> from os.path import abspath The dput-ng style "fully qualified path" to that function (abspath) would be:: os.path.abspath It's really that simple. .. note:: It's also worth noting dput-ng adds a few directories to sys.path to aid with debugging and distributing trivial scripts. For each directory in :data:`dput.core.CONFIG_LOCATIONS`, that directory plus "scripts" will be added to the system path, so (commonly) ``~/.dput.d/scripts`` and ``/etc/dput.d/scripts`` are valid Python path roots to dput-ng. OK, let's do an example. ------------------------ Let's do a simple checker -- one that fails out if Arno is the maintainer:: def check_for_arno(changes, profile, interface): """ The ``arno`` checker will explode in a firey mess if Arno tries to upload anything to the archive. This checker doesn't change it's behavior given any Profile codes. """ maintainer = changes['Maintainer'] if "arno@debian.org" in maintainer: raise HookException("Arno's not allowed to Upload.") I've saved this file to ``~/.dput.d/scripts/arno.py``. It should be noted that dput-ng can now import this file as ``arno``, and the command (from inside dput-ng) ``from arno import check_for_arno`` will work. Since we need to tell dput-ng about this hook, we need to drop it's def into a dput hook directory. Let's use our home directory again, even though it should be noted both ``/usr/share/dput-ng/`` and ``/etc/dput.d/`` will work as well. I've placed ``arno.json`` into ``~/.dput.d/hooks/arno.json``:: { "description": "Blow up if Arno's maintaining this package.", "path": "arno.check_for_arno", "pre": true } The ``pre`` key, or the ``post`` key must be present and set to a boolean. If no key is given, it assumes it's a ``pre`` checker. The ``path`` is the Python-importable path to the hook function, and ``description`` is for humans looking to get some information on the hook. We can make sure it works using ``dirt(1)``:: $ dirt info --hook arno The ``arno`` checker will explode in a firey mess if Arno tries to upload anything to the archive. This checker doesn't change it's behavior given any Profile codes. Remember, this pulls from the docstring, so please leave docstrings! OK. Now that dput-ng is aware of the plugin, we can add it to a profile by adding a "plus-key" to your profile choice. Let's add this to ``ftp-master``, since we want to make sure Arno never uploads there. Here's my (user-local) ftp-master config ``~/.dput.d/profiles/ftp-master.json``:: { "+hooks": [ "arno" ] } If you want to learn more about why this syntax works, I'd suggest checking out the :doc:`configs` documentation. So, let's try uploading:: $ dput [...] [...] running check-debs: makes sure the upload contains a binary package running checksum: verify checksums before uploading running suite-mismatch: check the target distribution for common errors running arno: Blow up if Arno's maintaining this package. Arno's not allowed to Upload. $ echo $? 1 Nice! dput-ng-1.7/docs/reference/configs.rst0000644000000000000000000001141412114414240014667 0ustar Configuration File Overview =========================== There are a few changes between dput and dput-ng's handling of configuration files. The changes can be a bit overwhelming, but stick to what's in here and it should all make great sense. High Level Changes ------------------ Firstly, you should know dput-ng fully supports the old dput.cf style configuration file. However, it also defines its own own, `JSON `_ encoded. Settings which are specific to dput-ng, in particular hooks and profiles can only be defined in dput-ng's configuration style. It is possible to run dput-ng with old-style configuration files only, with new-style configuration files only and even with shared profiles, where both new-style and old-style dput configuration files partially define behavior of a stanza. By default, dput-ng will look for configuration files in one of three places: ``/usr/share/dput-ng/``, ``/etc/dput.d/`` and ``~/.dput.d/``. Files in each location are additive, except in the case of a key conflict, in which case, the key is overridden by the next file. The idea here is that packages must ship defaults in ``/usr/share/dput-ng``. If the system admin wishes to override the defaults on a per-host basis, the file may be overridden in ``/etc/dput.d``. If a user wishes to override either of the decisions above, they may modify it in the local ``~/.dput.d`` directory. Defaults (e.g. the old [DEFAULT] section) are shared (new-style location is in ``profiles/DEFAULT.json``), so changing default behavior should affect the target, regardless of how it's defined. In the case of two defaults conflicting, the new-style configuration is chosen. Order of Loading ---------------- If all possible files and directories exist, this is order of loading of files: 1. /usr/share/dput-ng/ (new-style default profiles) 2. /etc/dput.d (new-style site-wide profiles), 3. /etc/dput.cf (old-style site-wide profiles) 4. ~/.dput.d (new-style local profiles) 5. ~/.dput.cf (old-style local profiles) 6. Any file supplied via command line. To remove a profile entirely, see operator handling below. Theory ------ New-style config files have two core attributes -- ``class`` and ``name``. For a upload target, that's known as a ``profile``. Technically speaking, any config file is located in ``${CONFIG_DIR}/class/name.json``. Keys can also be prefixed with one of three "operators". Operators tell dput-ng to preform an operation on the data structure when merging the layers together. Addition:: # global configuration block { "foo": [ 'one', 'two' ] } # local configuration block { "+foo": [ 'three' ] } # resulting data structure: { "foo": [ 'one', 'two', 'three' ] } Subtraction:: # global configuration block { "foo": [ 'one', 'two', 'three' ] } # local configuration block { "-foo": [ 'three' ] } # resulting data structure: { "foo": [ 'one', 'two' ] } Assignment:: # It should be noted that this *IS* the same as not prefixing the block # by an "=" operator. Please don't use this? Kay? It just uses up cycles # and is only here to be a logical extension of the last two. # global configuration block { "foo": [ 'one', 'two', 'three' ] } # local configuration block { "=foo": [ 'three' ] } # resulting data structure: { "foo": [ 'three' ] } Meta ---- The most complex part of these files is the "meta" target. Internally, this will fetch the config file from the ``metas`` class with the name provided in the config's ``meta`` attribute. The resulting object is placed under the config. Meta configs can declare another meta config, but will not work if it's self-referencing. Don't do that. Practice -------- OK, let's look at some real config files. I've implemented PPAs as a pure-JSON upload target. This file lives in profiles/ppa.json. It looks something like:: { "meta": "ubuntu", "fqdn": "ppa.launchpad.net", "incoming": "~%(ppa)s/ubuntu", "login": "anonymous", "method": "ftp" } You'll notice the old-style substring replacement is the same. While looking a bit deeper, you'll also notice that we inherit from the Ubuntu meta-class. Overriding default hook behavior ----------------------------------- It's idiomatic to just *extend* what you get from your parent (e.g. use the prefix operators ``+`` or ``-``, so that you don't have to duplicate the same list over and over. dput-ng-1.7/docs/reference/migrating.rst0000644000000000000000000000301612114414240015217 0ustar Migration guide from old-style dput =================================== Welcome! This is a helpful starting guide for anyone looking to switch from dput to dput-ng. dput-ng features a few interesting changes, so it's worthwhile to run through this helpful starting guide. Key points ---------- * dput's configuration files *are* supported, and *will* override any new-style configuration file. * Behavior of pre-upload checks *may* be different. * dput-ng maintains backwards compatibility with the old dput's command line flags. * dcut has a totally revamped interface, but is similar in spirit and usability of dput's dcut interface. * This package *replaces* old style dput. Big changes from dput --------------------- * Configuration can be defined in JSON. :doc:`configs` may be of some help. * More and better behaved checks are enabled by default, and more are ready for use out of the box, if you so wish. * post-upload hook and pre-upload checks (or hooks) may be written in Python, and have access to the objects which matter. For more on writing one, :doc:`hooks` may provide some insight. Stability Notes --------------- This *is not* finished. There are bits to be done, but this shows a decent amount of progress being made on the tool, and is mostly ready for limited use by technical people. * Bug reports are extremely welcome. * Ideas are extremely welcome. * Contributors are extremely welcome -- of all kinds (technical or otherwise) (see :doc:`contributing`) dput-ng-1.7/docs/index.rst0000644000000000000000000000456212114414240012416 0ustar Welcome to dput's documentation! ================================ `dput-ng `_ is a brand-new retake of the classic Debian tool, dput. We've made some important changes, which are documented here. Please get acquainted with the documentation, in order to fully understand the changes. The :doc:`reference/migrating` might be helpful for new users. Motivation ========== Many have asked "why rewrite dput", or "why not work with dput"? Frankly, when it comes down to it, we were concerned with the bitrot that is present in old dput, and decided to spend our time designing dput-ng to support all of our ideas from the ground up, rather then further mangling old dput's codebase to support them. As far as what features, the biggest improvements in our mind are: * Enhanced and configurable pre-upload checks baked in and enabled by default to make sure you do not accidentally upload a package which is not suitable for archives * Support for external third party checkers * Fragmented configuration, to allow external packages to include checks. * Real SFTP support * Dynamic checker behavior depending on host / profile * Support for all checksums, Debian Changes files support (MD5, SHA1, SHA256) * Full dcut support, including Debian Maintainer permission handling We're both really big fans of dput, so we've decided to maintain 100% compatibility with dput in dput-ng, as well as automagically reading from the old-style dput.cf conf files. You might see some behavior change, but we believe it to be in the spirit of the original incarnation of dput. All the new features and functionality is fully disable-able, and you should be able to use dput-ng just like you were before. dput-ng also features a lot of new features that might be of interest to Debian derivatives, such as the ability to add a new upload target (now called profiles) and unique checks, without having to fork dput. Changes which make extending dput downstream will likely be accepted in dput main. Please consider contributing. Documentation Index =================== Contents: .. toctree:: :maxdepth: 3 reference/index library/index Authors ======= The bulk of the work was done by `Arno `_ and `Paul `_ For a full list of contributors, please check the AUTHORS file shipped with your copy of dput-ng. dput-ng-1.7/docs/conf.py0000644000000000000000000001610412114414240012047 0ustar # -*- coding: utf-8 -*- import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'dput' copyright = u'2012, Paul Tagliamonte' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. release = '1.0.0' # XXX: Fix this to pull from ../ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'dputdoc' # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ('index', 'dput.tex', u'dput Documentation', u'Paul Tagliamonte', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'dput', u'dput Documentation', [u'Paul Tagliamonte'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'dput', u'dput Documentation', u'Paul Tagliamonte', 'dput', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' dput-ng-1.7/docs/Makefile0000644000000000000000000001266412114414240012217 0ustar # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dput.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dput.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/dput" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dput" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." dput-ng-1.7/docs/devel.sh0000755000000000000000000000037512114414240012211 0ustar #!/bin/bash last="" while [ true ]; do now=$(find -name "*rst" -type f -printf "%T@ %Tx %TX %p\n" | sort -n -r | head -1) if [ "$last" != "$now" ]; then make html >/dev/null echo "Updated." fi last=$now sleep 1 done dput-ng-1.7/.project0000644000000000000000000000055112114414240011266 0ustar dput-ng org.python.pydev.PyDevBuilder org.python.pydev.pythonNature dput-ng-1.7/requirements.txt0000644000000000000000000000013612114414240013102 0ustar validictory nose paramiko sphinx coverage # You also need python-debian, which isn't in pypi. dput-ng-1.7/setup.cfg0000644000000000000000000000011512114414240011434 0ustar [nosetests] detailed-errors=1 with-coverage=1 cover-package=dput nocapture=1 dput-ng-1.7/bin/0000755000000000000000000000000012160724715010402 5ustar dput-ng-1.7/bin/dcut0000755000000000000000000001151312160717773011276 0ustar #!/usr/bin/env python # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. import sys import argparse # A little temporary hack for those of us not using virtualenv import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import dput.core import dput.exceptions import dput.profile import dput.command from dput import upload_command parser = argparse.ArgumentParser(description=' Debian archive command file' 'upload tool') parser.add_argument('-d', '--debug', action='count', default=False, help="Enable debug messages. Repeat twice to increase the " "verbosity level") parser.add_argument('-f', '--force', action='store_true', help='Force an upload') parser.add_argument('-c', '--config', metavar="FILE", action='store', default=None, help='Configuration file to parse') parser.add_argument('host', metavar="HOST", action='store', default=None, help="Target host to upload a package", nargs="?") parser.add_argument('-m', '--maintainer', metavar="MAINTAINER", help="Use MAINTAINER (full name and email) for the " "uploader field and gpg key selection. This has no effect" " when the 'upload' command is used", action='store') parser.add_argument('-k', '--keyid', metavar="KEYID", help="Use KEYID for signing. Default is to use DEBEMAIL " " and DEBNAME, or whatever was provided with --maintainer." " This has no effect when the 'upload' command is used", action='store') parser.add_argument('-O', '--output', metavar="FILENAME", help="Write commands file to FILENAME instead of " "uploading. This option should not be used with " "the 'upload' command. FILENAME won't be overwritten if it " "exists", action='store') parser.add_argument('-P', '--passive', help="Use passive FTP instead of active", action='store_true') parser.add_argument('-s', '--simulate', action='count', default=False, help="Simulate the upload only. Repeat twice to increase " "the level of simulation. Provided once runs pre-upload " "checks, provided twice runs pre-upload checks and network" " set-up without actually uploading files") parser.add_argument('-v', '--version', help="Print version information and exit", action='store_true') args = parser.parse_known_args() known = args[0] if known.host in dput.command.find_commands(): known.host = None # likely a target, not a profile if known.host: profile = dput.profile.load_profile(known.host) else: profile = dput.profile.load_profile(None) subparsers = parser.add_subparsers(help='Supported commands') command_registry = dput.command.load_commands(profile) for command in command_registry: (cmd_name, cmd_purpose) = command.name_and_purpose() command_parser = subparsers.add_parser(cmd_name, help=cmd_purpose) command_parser.set_defaults(command=command) command.register(command_parser) args = parser.parse_args() if args.config: dput.core.DPUT_CONFIG_LOCATIONS[args.config] = 1 if args.debug: dput.core._enable_debugging(args.debug) try: upload_command(args) except dput.exceptions.DputConfigurationError as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(2) except dput.exceptions.DcutError as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(3) except dput.exceptions.HookException as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(1) except EnvironmentError as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(2) except KeyboardInterrupt: sys.exit(0) dput-ng-1.7/bin/dput0000755000000000000000000001230312160724715011303 0ustar #!/usr/bin/env python # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. import sys import argparse # A little temporary hack for those of us not using virtualenv import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import dput.core import dput.changes import dput.exceptions from dput import upload_package from dput.profile import parse_overrides parser = argparse.ArgumentParser(description='Debian package upload tool') parser.add_argument('-d', '--debug', action='count', default=False, help="Enable debug messages. Repeat twice to increase the " "verbosity level") parser.add_argument('-c', '--config', metavar="FILE", action='store', default=None, help='Configuration file to parse') parser.add_argument('-D', '--dinstall', action='store_true', help='(silently ignored)') parser.add_argument('-e', '--delayed', action='store', metavar="DAYS", help='Upload to a delayed queue. Takes an argument from 0 to 15', type=int, choices=range(0, 16)) parser.add_argument('-F', '--full-upload-log', action='store_true', help='Write more verbose .upload logs') parser.add_argument('-f', '--force', action='store_true', help='Force an upload') parser.add_argument('-l', '--lintian', action='store_true', help='Run lintian before upload (deprecated)') parser.add_argument('-U', '--no-upload-log', action='store_true', help='Do not write an .upload log after uploading') parser.add_argument('-o', '--check-only', action='store_true', help='Only check the package') parser.add_argument('-O', '--override', action='append', help='override profile key') parser.add_argument('-S', '--unset', action='append', help='override profile key by unsetting its value') parser.add_argument('-P', '--passive', action='store_true', help='Use passive mode for ftp uploads') parser.add_argument('-s', '--simulate', action='count', default=False, help="Simulate the upload only. Repeat twice to increase " "the level of simulation. Provided once runs pre-upload " "checks, provided twice runs pre-upload checks and network" " set-up without actually uploading files") parser.add_argument('-u', '--unchecked', action='store_true', help='Don\'t check GnuPG signature') parser.add_argument('-v', '--version', action='store_true', help='(silently ignored)') parser.add_argument('-V', '--check-version', action='store_true', help='(ignored)') parser.add_argument('host', metavar="HOST", action='store', default=None, help="Target host to upload a package", nargs="?") parser.add_argument('changes', metavar="CHANGES-FILE", action='store', default=None, nargs='+', help="A Debian .changes file") args = parser.parse_args() if args.host and args.host.endswith(".changes") and os.path.isfile(args.host): args.changes.insert(0, args.host) args.host = None if args.config: dput.core.DPUT_CONFIG_LOCATIONS[args.config] = 1 if args.debug: dput.core._enable_debugging(args.debug) try: overrides = [] if args.unset: for arg in args.unset: overrides.append("-%s" % (arg)) if args.override: for arg in args.override: overrides.append(arg) args.override = parse_overrides(overrides) for changes in args.changes: changes = dput.changes.parse_changes_file( changes, os.path.dirname(changes) ) upload_package(changes, args) # XXX: avoid only uploading one and breaking due to an UploadException? except dput.exceptions.DputConfigurationError as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(2) except dput.exceptions.UploadException as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(3) except dput.exceptions.HookException as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(1) except EnvironmentError as e: dput.core.logger.critical(str(e)) dput.core.maybe_print_traceback(args.debug, sys.exc_info()) sys.exit(2) except KeyboardInterrupt: sys.exit(0) dput-ng-1.7/bin/dirt0000755000000000000000000001277312160717773011312 0ustar #!/usr/bin/env python # D.I.R.T. # arguments: # --blame target # --hosts # --help-checker # --help-processor from __future__ import print_function import os import sys import json import argparse # A little temporary hack for those of us not using virtualenv sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from dput.hook import hook_docs from dput.util import get_configs, load_config, validate_object from dput.exceptions import DputConfigurationError from dput.profile import get_blame_map, profiles, load_profile parser = argparse.ArgumentParser() parser = argparse.ArgumentParser(description='dput information retrieval tool') subparsers = parser.add_subparsers(help='sub-command help') parser_hosts = subparsers.add_parser('hosts', help='print the lists of hosts that dput knows about') parser_hosts.set_defaults(command="hosts") parser_blame = subparsers.add_parser('blame', help='get information on where dput is finding keys') parser_blame.set_defaults(command="blame") parser_blame.add_argument('-p', '--profile', metavar='PROFILE_NAME', help='name of the hook to retrieve information from', required=True) parser_list = subparsers.add_parser('list', help='list all the hooks registered to dput') parser_list.set_defaults(command="list") parser_info = subparsers.add_parser('info', help='get some help on a hook') parser_info.set_defaults(command="info") parser_info.add_argument('-H', '--hook', metavar='HOOK_NAME', help='name of the hook to retrieve information from', required=True) #XXX: stubbing for now #group.add_argument( # '--remove-hook', # nargs=2, # action='store', # help='Remove a hook from a given profile', # metavar='HOOK' #) # #group.add_argument( # '--add-hook', # nargs=2, # action='store', # help='Add a hook to a given profile', # metavar='HOOK' #) args = parser.parse_args() print("\nWARNING: This command is not completed yet. Interface and behavior " "changes are expected in future releases\n") if args.command == 'blame': if args.profile not in profiles(): print("No such target.") sys.exit(1) print(json.dumps(get_blame_map(args.profile), sort_keys=True, indent=4)) sys.exit(0) if args.command == 'hosts': default = load_profile(None) print() print("Default method: %s" % (default['method'])) print() for config in profiles(): obj = load_profile("%s:%s" % (config, config)) # ^^^^^^ fake arg for others if not "fqdn" in obj: # likely localhost obj['fqdn'] = 'localhost' string = "{name} => {fqdn} (Upload method: {method})".format(**obj) print(string) print() sys.exit(0) if args.command == 'info': try: docs = hook_docs(args.hook) if docs == "": print("Sorry, the author didn't provide help on this module.") sys.exit(0) print(docs) except DputConfigurationError: print("No such hook '%s'." % (args.hook)) sys.exit(1) if args.command == 'list': hook_list = {'pre': {}, 'post': {}} for config in get_configs('hooks'): hook = load_config('hooks', config) validate_object('plugin', hook, "hooks/%s" % (config)) if 'pre' in hook: hook_list['pre'][config] = hook if 'post' in hook: hook_list['post'][config] = hook for hook_type in hook_list: print("%s-upload hooks:" % (hook_type)) for hook in hook_list[hook_type]: print("\t%s: %s" % (hook, hook_list[hook_type][hook]['description'])) print() sys.exit(0) # Dear lord of all that is holy, the kruft below *needs* to be moved into # proper dput config objects. We should really just grab the local foo, and # deal with that mess on the fly. Don't put it there until it's factored in # nicely. # - PRT def _read_file(fpath): obj = {} if os.path.exists(fpath): obj = json.load(open(fpath, 'r')) return obj def _write_file(fpath, obj): d = os.path.dirname(fpath) if not os.path.exists(d): os.makedirs(d) open(fpath, 'w').write(json.dumps(obj, sort_keys=True, indent=4)) #if args.add_hook: # hook, target = args.add_hook # # sanity check target, now. # # sanity check hook, now. # config = os.path.expanduser( # "~/.dput.d/profiles/%s.json" % (target) # XXX: Do this right. # ) # obj = _read_file(config) # if not '+hooks' in obj: # obj['+hooks'] = [] # if hook not in obj['+hooks']: # print("Added %s to the config." % (hook)) # obj['+hooks'].append(hook) # else: # print("%s is already in the config." % (hook)) # # _write_file(config, obj) # sys.exit(0) #if args.remove_hook: # hook, target = args.remove_hook # # sanity check target, now. # # sanity check hook, now. # config = os.path.expanduser( # "~/.dput.d/profiles/%s.json" % (target) # XXX: Do this right. # ) # obj = _read_file(config) # # if 'hooks' in obj: # if hook in obj['hooks']: # obj['hooks'].remove(hook) # # elif '+hooks' in obj: # if hook in obj['+hooks']: # obj['+hooks'].remove(hook) # else: # if not '-hooks' in obj: # obj['-hooks'] = [] # # obj['-hooks'].append(hook) # # _write_file(config, obj) # sys.exit(0) dput-ng-1.7/dput/0000755000000000000000000000000012241262032010573 5ustar dput-ng-1.7/dput/hooks/0000755000000000000000000000000012241261643011725 5ustar dput-ng-1.7/dput/hooks/gpg.py0000644000000000000000000000536512160717773013077 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.core import logger from dput.exceptions import (ChangesFileException, HookException) class GPGCheckerError(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``gpg`` checker encounters an issue. """ pass def check_gpg_signature(changes, profile, interface): """ The ``gpg`` checker is a stock dput checker that checks packages intended for upload for a GPG signature. Profile key: ``gpg`` Example profile:: { "allowed_keys": [ "8F049AD82C92066C7352D28A7B585B30807C2A87", "B7982329" ] } ``allowed_keys`` is an optional entry which contains all the keys that may upload to this host. This can come in handy if you use more then one key to upload to more then one host. Use any length of the last N chars of the fingerprint. """ if "allow_unsigned_uploads" in profile: if profile['allow_unsigned_uploads']: logger.info("Not checking GPG signature due to " "allow_unsigned_uploads being set.") return gpg = {} if 'gpg' in profile: gpg = profile['gpg'] try: key = changes.validate_signature() if 'allowed_keys' in gpg: allowed_keys = gpg['allowed_keys'] found = False for k in allowed_keys: if k == key[-len(k):]: logger.info("Key %s is trusted to upload to this host." % ( k )) found = True if not found: raise GPGCheckerError("Key %s is not in %s" % ( key, allowed_keys )) except ChangesFileException as e: raise GPGCheckerError( "No valid signature on %s: %s" % (changes.get_filename(), e) ) dput-ng-1.7/dput/hooks/distribution.py0000644000000000000000000001460412213141156015016 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. import re from dput.core import logger from dput.util import load_config from dput.exceptions import HookException from dput.interface import BUTTON_NO class BadDistributionError(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``allowed-distribution`` checker encounters an issue. """ pass class SuiteMismatchError(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``suite-mismatch`` checker encounters an issue. """ pass def check_allowed_distribution(changes, profile, interface): """ The ``allowed-distribution`` checker is a stock dput checker that checks packages intended for upload for a valid upload distribution. Profile key: none Example profile:: { ... "allowed_distributions": "(?!UNRELEASED)", "distributions": ["unstable", "testing"], "disallowed_distributions": [] ... } The allowed_distributions key is in Python ``re`` syntax. """ allowed_block = profile.get('allowed-distribution', {}) suite = changes['Distribution'] if 'allowed_distributions' in profile: srgx = profile['allowed_distributions'] if re.match(srgx, suite) is None: logger.debug("Distribution does not %s match '%s'" % ( suite, profile['allowed_distributions'] )) raise BadDistributionError("'%s' doesn't match '%s'" % ( suite, srgx )) if'distributions' in profile: allowed_dists = profile['distributions'] if suite not in allowed_dists.split(","): raise BadDistributionError( "'%s' doesn't contain distribution '%s'" % ( suite, profile['distributions'] )) if 'disallowed_distributions' in profile: disallowed_dists = profile['disallowed_distributions'] if suite in disallowed_dists: raise BadDistributionError("'%s' is in '%s'" % ( suite, disallowed_dists)) if 'codenames' in profile and profile['codenames']: codenames = load_config('codenames', profile['codenames']) blocks = allowed_block.get('codename-groups', []) if blocks != []: failed = True for block in blocks: names = codenames.get(block, []) if suite in names: failed = False if failed: raise BadDistributionError("`%s' not in the codename group" % ( suite )) def check_protected_distributions(changes, profile, interface): """ The ``protected distributions`` checker is a stock dput checker that makes sure, users intending an upload for a special care archive ( testing-proposed-updates, stable-security, etc.) did really follow the archive policies for that. Profile key: none """ # XXX: This check does not contain code names yet. We need a global way # to retrieve and share current code names. suite = changes['Distribution'] query_user = False release_team_suites = ["testing-proposed-updates", "proposed-updates", "stable", "testing"] if suite in release_team_suites: msg = "Are you sure to upload to %s? Did you coordinate with the " \ "Release Team before your upload?" % (suite) error_msg = "Aborting upload to Release Team managed suite upon " \ "request" query_user = True security_team_suites = ["stable-security", "oldstable-security", "testing-security"] if suite in security_team_suites: msg = "Are you sure to upload to %s? Did you coordinate with the " \ "Security Team before your upload?" % (suite) error_msg = "Aborting upload to Security Team managed suite upon " \ "request" query_user = True if query_user: logger.trace("Querying the user for input. The upload targets a " "protected distribution") if not interface.boolean('Protected Checker', msg, default=BUTTON_NO): raise BadDistributionError(error_msg) else: logger.warning("Uploading with explicit confirmation by the user") else: logger.trace("Nothing to do for checker protected_distributions") def check_distribution_matches(changes, profile, interface): """ The ``suite-mismatch`` checker is a stock dput checker that checks packages intended for upload for matching Distribution and last Changelog target. Profile key: none This checker simply verified that the Changes' Distribution key matches the last changelog target. If the mixup is between experimental and unstable, it'll remind you to pass ``-c unstable -d experimental`` to sbuild. """ changelog_distribution = changes.get("Changes").split()[2].strip(';') intent = changelog_distribution.strip() actual = changes.get("Distribution").strip() if intent != actual: logger.info("Upload is targeting %s but the changes will hit %s" % ( intent, actual)) err = "Upload is targeting `%s', but the changes will hit `%s'." % ( intent, actual ) if intent == 'experimental' and ( actual == 'unstable' or actual == 'sid' ): err += \ "\nLooks like you forgot -d experimental when invoking sbuild." raise SuiteMismatchError(err) dput-ng-1.7/dput/hooks/deb.py0000644000000000000000000000601412203764026013033 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.core import logger from dput.exceptions import HookException class BinaryUploadError(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``check-debs`` checker encounters an issue. """ pass def check_debs_in_upload(changes, profile, interface): """ The ``check-debs`` checker is a stock dput checker that checks packages intended for upload for .deb packages. Profile key: ``foo`` Example profile:: { "skip": false, "enforce": "debs" } ``skip`` controls if the checker should drop out without checking for anything at all. ``enforce`` This controls what we check for. Valid values are "debs" or "source". Nonsense values will cause an abort. """ debs = {} if 'check-debs' in profile: debs = profile['check-debs'] section = changes.get_section() BYHAND = (section == "byhand") logger.debug("Is BYHAND: %s" % (BYHAND)) logger.debug(" section value: %s" % (section)) if 'skip' in debs and debs['skip']: logger.debug("Skipping deb checker.") return enforce_debs = True if 'enforce' in debs: model = debs['enforce'] if model == 'debs': enforce_debs = True elif model == 'source': enforce_debs = False else: logger.warning("Garbage value for check-debs/enforce - is %s," " valid values are `debs` and `source`. Skipping" " checks." % (model)) return else: logger.warning("No `enforce` key in check-debs. Skipping checks.") return has_debs = False for fil in changes.get_files(): xtns = ['.deb', '.udeb'] for xtn in xtns: if fil.endswith(xtn): has_debs = True if enforce_debs and not has_debs and not BYHAND: raise BinaryUploadError( "There are no .debs in this upload, and we're enforcing them." ) if not enforce_debs and has_debs: raise BinaryUploadError( "There are .debs in this upload, and enforcing they don't exist." ) dput-ng-1.7/dput/hooks/impatient.py0000644000000000000000000000323012160717773014301 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Tell you when dinstall is next """ import datetime as dt import urllib2 import time import re URL_ROOT = "http://ftp-master.debian.org/dinstall.html" def check_next_install(changes, profile, interface): """ """ #data = urllib2.urlopen(URL_ROOT).read() #times = re.finditer(r"\d{2}:\d{2}", data) # XXX: Perhaps re-do it this way? Perhaps? times = ['01:52', '07:52', '13:52', '19:52'] now = dt.datetime.utcnow() for t in times: day, month, year = (dt.datetime.strftime(now, x) for x in ["%d", "%m", "%Y"]) t = "%s %s %s %s" % (t, day, month, year) nt = dt.datetime.strptime(t, "%H:%M %d %m %Y") d = nt - now if d.total_seconds() > 0: interface.message("next dinstall", "Next dinstall run in %s" % d) break dput-ng-1.7/dput/hooks/__init__.py0000644000000000000000000000146512114414240014034 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. dput-ng-1.7/dput/hooks/lintian.py0000644000000000000000000001007212114414240013725 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Lintian checker implementation """ import subprocess from dput.core import logger from dput.exceptions import HookException, DputConfigurationError from dput.interface import BUTTON_NO class LintianHookException(HookException): pass def process(output): ret = [] for line in output.splitlines(): flag, line = line.split(":", 1) flag = flag.strip() if flag == "N": continue line = line.strip() component, line = line.split(":", 1) component = component.strip() line = line.strip() payload = line.split(" ", 1) if len(payload) > 1: tag, info = payload else: tag = payload[0] info = "" tag = tag.strip() info = info.strip() ret.append({ "severity": flag, "component": component, "tag": tag, "info": info }) return ret def lint(path, pedantic=False, info=False, experimental=False): args = ["lintian", "--show-overrides"] if pedantic: args.append("--pedantic") if info: args.append("-I") if experimental: args.append("-E") args.append(path) try: output = subprocess.check_output(args) except subprocess.CalledProcessError as e: output = e.output except OSError as e: logger.warning("Failed to execute lintian: %s" % (e)) raise DputConfigurationError("lintian: %s" % (e)) return process(output) def lintian(changes, profile, interface): """ The ``lintian`` checker is a stock dput checker that checks packages intended for upload for common mistakes, using the static checking tool, `lintian `. Profile key: ``lintian`` Example profile:: { "run_lintian": true "lintian": { } } No keys are current supported, but there are plans to set custom ignore lists, etc. """ if "run_lintian" in profile: logger.warning("Setting 'run_lintian' is deprecated. " "Please configure the lintian checker instead.") if not profile['run_lintian']: # XXX: Broken. Fixme. logger.info("skipping lintian checking, enable with " "run_lintian = 1 in your dput.cf") return tags = lint( changes._absfile, pedantic=True, info=True, experimental=True ) sorted_tags = {} for tag in tags: if not tag['severity'] in sorted_tags: sorted_tags[tag['severity']] = {} if tag['tag'] not in sorted_tags[tag['severity']]: sorted_tags[tag['severity']][tag['tag']] = tag tags = sorted_tags # XXX: Make this configurable if not "E" in tags: return for tag in set(tags["E"]): print " - %s: %s" % (tags["E"][tag]['severity'], tag) inp = interface.boolean('Lintian Checker', 'Upload despite of Lintian finding issues?', default=BUTTON_NO) if not inp: raise LintianHookException( "User didn't own up to the " "Lintian issues" ) else: logger.warning("Uploading with outstanding Lintian issues.") dput-ng-1.7/dput/hooks/checksum.py0000644000000000000000000000333412160717773014116 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.core import logger from dput.exceptions import (ChangesFileException, HookException) class HashValidationError(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``checksum`` checker encounters an issue. """ pass def validate_checksums(changes, profile, interface): """ The ``checksum`` checker is a stock dput checker that checks packages intended for upload for correct checksums. This is actually the most simple checker that exists. Profile key: none. Example profile:: { ... "hash": "md5" ... } The hash may be one of md5, sha1, sha256. """ try: changes.validate_checksums(check_hash=profile["hash"]) except ChangesFileException as e: raise HashValidationError( "Bad checksums on %s: %s" % (changes.get_filename(), e) ) dput-ng-1.7/dput/hooks/distro_info_checks.py0000644000000000000000000000636612241260554016151 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2013 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.core import logger from dput.exceptions import HookException try: from distro_info import (DebianDistroInfo, UbuntuDistroInfo, DistroDataOutdated) except ImportError: logger.warning('Uploading to Ubuntu requires python-distro-info to be ' 'installed') raise class UnknownDistribution(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``supported-distribution`` checker encounters an issue. """ pass class UnsupportedDistribution(HookException): """ Subclass of the :class:`dput.exceptions.HookException`. Thrown if the ``supported-distribution`` checker finds a release that isn't supported. """ pass def check_supported_distribution(changes, profile, interface): """ The ``supported-distribution`` checker is a stock dput checker that checks packages intended for upload for a valid upload distribution. Profile key: supported-distribution """ suite = changes['Distribution'] if profile.get('codenames'): if '-' in suite: release, pocket = suite.split('-', 1) else: release, pocket = suite, 'release' codenames = profile['codenames'] if codenames == 'ubuntu': distro_info = UbuntuDistroInfo() pockets = profile['supported-distribution'] logger.critical(pockets) if pocket not in pockets['known']: raise UnknownDistribution("Unkown pocket: %s" % pocket) if pocket not in pockets['allowed']: raise UnknownDistribution( "Uploads aren't permitted to pocket: %s" % pocket) elif codenames == 'debian': distro_info = DebianDistroInfo() else: raise UnknownDistribution("distro-info doesn't know about %s" % codenames) try: codename = distro_info.codename(release, default=release) if codename not in distro_info.all: raise UnsupportedDistribution('Unknown release %s' % release) if codename not in distro_info.supported(): raise UnsupportedDistribution('Unsupported release %s' % release) except DistroDataOutdated: logger.warn('distro-info is outdated, ' 'unable to check supported releases') dput-ng-1.7/dput/profile.py0000644000000000000000000002207512240215460012615 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Profile implementation & routines. This handles all external access of the Profile data. """ import logging import re import shlex import dput.core from dput.core import logger from dput.config import AbstractConfig from dput.configs.dputcf import DputCfConfig from dput.configs.dputng import DputProfileConfig from dput.exceptions import DputConfigurationError from dput.util import _config_cleanup, validate_object classes = { "dputng": DputProfileConfig, "dputcf": DputCfConfig } def _blame_map(obj, blame): ret = {} for key in obj: val = obj[key] if isinstance(val, dict): ret[key] = _blame_map(obj[key], blame) if isinstance(val, list): vals = {} for v in val: vals[v] = blame ret[key] = vals else: ret[key] = blame return ret class MultiConfig(AbstractConfig): """ Multi-configuration abstraction. This helps abstract the underlying configurations from the user. This is a subclass of :class:`dput.config.AbstractConfig` """ def __init__(self): configs = [] for config in dput.core.CONFIG_LOCATIONS: configs.append({ "type": "dputng", "loc": config, "weight": dput.core.CONFIG_LOCATIONS[config] }) for config in dput.core.DPUT_CONFIG_LOCATIONS: configs.append({ "type": "dputcf", "loc": config, "weight": dput.core.DPUT_CONFIG_LOCATIONS[config] }) configs = sorted(configs, key=lambda c: c['weight']) configs.reverse() self.preload(configs) def set_replacements(self, replacements): """ See :meth:`dput.config.AbstractConfig.set_replacements` """ for config in self.configs: config.set_replacements(replacements) def preload(self, objs): """ See :meth:`dput.config.AbstractConfig.preload` """ configs = [] for obj in objs: configs.append( classes[obj['type']]( [obj['loc']] ) ) self.configs = configs defaults_blame = {} defaults = {} for config in configs: defaults.update(config.get_defaults()) defaults = _config_cleanup(defaults) defaults_blame.update( _blame_map(config.get_defaults(), "%s (%s)" % ( config.path, 'DEFAULT' )) ) self.defaults = defaults self.defaults_blame = defaults_blame def get_defaults(self): """ See :meth:`dput.config.AbstractConfig.get_defaults` """ return self.get_config("DEFAULT") def get_config(self, name): """ See :meth:`dput.config.AbstractConfig.get_config` """ logger.trace("Loading entry %s" % (name)) ret = self.defaults.copy() for config in self.configs: obj = config.get_config(name) logger.trace(obj) ret.update(obj) ret = _config_cleanup(ret) logger.trace('Rewrote to:') logger.trace(obj) if logger.isEnabledFor(logging.DEBUG): logger.debug("Got configuration: %s" % (name)) for key in ret: logger.debug("\t%s: %s" % (key, ret[key])) validate_object('config', ret, 'profiles/%s' % (name)) return ret def get_blame(self, name): ret = self.defaults_blame for config in self.configs: obj = config.get_config(name, ignore_errors=True) ret.update(_blame_map(obj, "%s (%s)" % (config.path, name))) return ret def get_config_blocks(self): """ See :meth:`dput.config.AbstractConfig.get_config_blocks` """ ret = set() for config in self.configs: for block in config.get_config_blocks(): ret.add(block) if "DEFAULT" in ret: ret.remove("DEFAULT") return ret _multi_config = None def load_profile(host): """ Load a profile, for a given host ``host``. In the case where ``host`` has a ":", that'll be treated as an expansion for config strings. For instance: ``ppa:paultag/fluxbox`` will expand any ``%(ppa)s`` strings to ``paultag/fluxbox``. Comes in super handy. """ global _multi_config repls = {} if host and ":" in host: host, arg = host.split(":", 1) repls[host] = arg if _multi_config is None: _multi_config = MultiConfig() config = _multi_config config.set_replacements(repls) configs = config.get_config_blocks() if host in configs: return config.get_config(host) if host is not None: raise DputConfigurationError("Error, was given host, " "but we don't know about it.") for block in configs: try: obj = config.get_config(block) except DputConfigurationError: continue # We don't have fully converted blocks. host_main = obj.get('default_host_main') if host_main: # If we have a default_host_main, let's return that. return config.get_config(host_main) return config.get_config("ftp-master") def profiles(): """ Get a list of all profiles we know about. It returns a set of strings, which can be accessed with :func:`load_profile` """ global _multi_config if _multi_config is None: _multi_config = MultiConfig() config = _multi_config configs = config.get_config_blocks() if "DEFAULT" in configs: configs.remove("DEFAULT") return configs def get_blame_map(name): global _multi_config if _multi_config is None: _multi_config = MultiConfig() config = _multi_config configs = config.get_blame(name) return configs def parse_overrides(overrides): """ Translate a complex string structure into a JSON like object. For example this function would translate foo.bar=baz like strings into objects overriding the JSON profile. Basically this function function will take any object separated by a dot on the left side as a dict object, whereas the terminal value on the right side is taken literally. """ # This function involves lots of black magic. # Generally we expect a foo.bar=value format, with foo.bar being a profile # key and value being $anything. # However, people might provide that in weird combinations such as # 'foo.bar = "value value"' override_obj = {} for override in overrides: parent_obj = override_obj if override.find("=") > 0 or override.startswith("-") > 0: (profile_key, profile_value) = (None, None) if override.startswith("-"): profile_key = override[1:] profile_value = None else: (profile_key, profile_value) = override.split("=", 1) profile_value = shlex.split(profile_value) profile_key = re.sub('\s', '', profile_key) profile_key = profile_key.split(".") last_item = profile_key.pop() for key in profile_key: if not key in parent_obj: parent_obj[key] = {} parent_obj = parent_obj[key] if isinstance(parent_obj, list): raise DputConfigurationError("Ambiguous override: object %s " "can either be a composite type or a terminal, not both" % (last_item)) if not last_item in parent_obj: parent_obj[last_item] = [] if last_item in parent_obj and not ( isinstance(parent_obj[last_item], list)): raise DputConfigurationError("Ambiguous override: object %s " "can either be a composite type or a terminal, not both" % (last_item)) parent_obj[last_item].append(profile_value) else: raise DputConfigurationError( "Profile override %s does not seem to match the " "expected format" % override) return override_obj dput-ng-1.7/dput/core.py0000644000000000000000000000704512160717773012124 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Stuff that everything uses and shouldn't keep pulling on their own. """ import sys import os.path import logging import traceback import getpass import dput.logger from logging.handlers import RotatingFileHandler # used for searching for config files. place in order of precedence CONFIG_LOCATIONS = { "/usr/share/dput-ng/": 30, "/etc/dput.d/": 20, os.path.expanduser("~/.dput.d"): 10, "skel/": 100 } """ Locations to look for JSON-ey config files. Under each directory may exist a ``class``, which is a folder full of json files, which may be loaded. The order dicates which has the most precedence. """ DPUT_CONFIG_LOCATIONS = { "/etc/dput.cf": 15, os.path.expanduser("~/.dput.cf"): 5 } """ Locations to look for old-style dput.cf configuration files. """ SCHEMA_DIRS = [ "/usr/share/dput-ng/schemas", "skel/schemas" ] """ validictory schemas """ # logging routines logging.setLoggerClass(dput.logger.DputLogger) logger = logging.getLogger("dput") """ Logger, for general output and stuff. """ logger.setLevel(dput.logger.TRACE) # basic config _ch = logging.StreamHandler() _ch.setLevel(logging.INFO) _formatter = logging.Formatter( '%(message)s') _ch.setFormatter(_formatter) def _write_upload_log(logfile, full_log): upload_log_formatter = logging.Formatter( "%(asctime)s - dput[%(process)d]: " "%(module)s.%(funcName)s - %(message)s" ) upload_log_handler = RotatingFileHandler(logfile) upload_log_handler.setFormatter(upload_log_formatter) if full_log: upload_log_handler.setLevel(logging.DEBUG) else: upload_log_handler.setLevel(logging.INFO) logger.addHandler(upload_log_handler) def _enable_debugging(level): _ch = logging.StreamHandler() if level == 1: _ch.setLevel(logging.DEBUG) if level >= 2: _ch.setLevel(dput.logger.TRACE) _formatter = logging.Formatter( '[%(levelname)s] %(created)f: (%(funcName)s) %(message)s') _ch.setFormatter(_formatter) logger.addHandler(_ch) logger.addHandler(_ch) def mangle_sys(): for root in CONFIG_LOCATIONS: pth = "%s/scripts" % (root) pth = os.path.abspath(pth) if pth not in sys.path: logger.debug("Loading external script location %s" % (pth)) sys.path.insert(0, pth) def maybe_print_traceback(debug_level, stack): if debug_level > 1: tb = traceback.format_tb(stack[2]) logger.trace("Traceback:") for level in tb: for tier in level.split("\n"): logger.trace(tier) def get_local_username(): try: local_user = getpass.getuser() except Exception as e: logger.warn("Could not determine local username: %s" % e) local_user = None return local_user dput-ng-1.7/dput/commands/0000755000000000000000000000000012241261643012403 5ustar dput-ng-1.7/dput/commands/breakthearchive.py0000644000000000000000000000432212114414240016075 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.commands.dm import generate_dak_commands_name from dput.interface import BUTTON_NO class BreakTheArchiveCommandCommandError(DcutError): pass class BreakTheArchiveCommand(AbstractCommand): def __init__(self, interface): super(BreakTheArchiveCommand, self).__init__(interface) self.cmd_name = "break-the-archive" self.cmd_purpose = "break the archive (no, really)" def generate_commands_name(self, profile): return generate_dak_commands_name(profile) def register(self, parser, **kwargs): pass def produce(self, fh, args): fh.write("\n") # yes, this newline matters fh.write("Action: %s\n" % (self.cmd_name)) def validate(self, args): if args.force: return self.interface.message( 'WARNING: Dangerous command', "Break the archive is totally dangerous! Make sure you know " "what you are doing before proceeding." ) if not self.interface.boolean( 'Break the Archive command', "Do you really want to break the archive?", default=BUTTON_NO ): raise BreakTheArchiveCommandCommandError( "Aborted by user request" ) def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/__init__.py0000644000000000000000000000147012114414240014506 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. dput-ng-1.7/dput/commands/dm.py0000644000000000000000000001407712240211077013361 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. import time import os.path from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.core import logger, get_local_username from dput.util import run_command DM_KEYRING = "/usr/share/keyrings/debian-maintainers.gpg" class DmCommandError(DcutError): pass def generate_dak_commands_name(profile): # for debianqueued: $login-$timestamp.commands # for dak: $login-$timestamp.dak-commands the_file = "%s-%s.dak-commands" % (get_local_username(), int(time.time())) # XXX: override w/ DEBEMAIL (if DEBEMAIL is @debian.org?) logger.trace("Commands file will be named %s" % (the_file)) return the_file class DmCommand(AbstractCommand): def __init__(self, interface): super(DmCommand, self).__init__(interface) self.cmd_name = "dm" self.cmd_purpose = "manage Debian Mantainer (DM) permissions" def generate_commands_name(self, profile): return generate_dak_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('--uid', action='store', default=None, dest="dm", help="Name, e-mail or fingerprint of an existing " "Debian Maintainer. Use a full fingerprint together " "with --force if you want to bypass any argument " "validation, causing dcut to take the argument " "literally as is.", required=True) parser.add_argument('--allow', metavar="PACKAGES", action='store', default=None, help="Source package(s) where permissions to " "upload should be granted", nargs="*") parser.add_argument('--deny', metavar="PACKAGES", action='store', default=None, help="Source package(s) where permissions to " "upload should be denied", nargs="*") def produce(self, fh, args): fh.write("\n") # yes, this newline matters fh.write("Action: %s\n" % (self.cmd_name)) fh.write("Fingerprint: %s\n" % (args.dm)) if args.allow: fh.write("Allow: ") for allowed_packages in args.allow: fh.write("%s " % (allowed_packages)) fh.write("\n") if args.deny: fh.write("Deny: ") for denied_packages in args.deny: fh.write("%s " % (denied_packages)) fh.write("\n") def validate(self, args): if args.force: return if not os.path.exists(DM_KEYRING): raise DmCommandError( "To manage DM permissions, the `debian-keyring' " "keyring package must be installed. " "File %s does not exist" % (DM_KEYRING) ) return # I HATE embedded functions. But OTOH this function is not usable # somewhere else, so... def pretty_print_list(tuples): fingerprints = "" for entry in tuples: fingerprints += "\n- %s (%s)" % entry return fingerprints # TODO: Validate input. Packages must exist (i.e. be not NEW) (out, err, exit_status) = run_command([ "gpg", "--no-options", "--no-auto-check-trustdb", "--no-default-keyring", "--list-key", "--with-colons", "--fingerprint", "--keyring", DM_KEYRING, args.dm ]) if exit_status != 0: raise DmCommandError("DM fingerprint lookup " "for argument %s failed. " "GnuPG returned error: %s" % (args.dm, err)) possible_fingerprints = [] current_uid = None next_line_contains_fpr = False gpg_out = out.split("\n") for line in gpg_out: if next_line_contains_fpr: assert(line.startswith("fpr")) parsed_fingerprint = line.split(":") # fpr:::::::::CACE80AE01512F9AE8AB80D61C01F443C9C93C5A: possible_fingerprints.append((current_uid, parsed_fingerprint[9],)) next_line_contains_fpr = False continue elif not line.startswith("pub"): continue else: # will give a line like: # pub:-:4096:1:7B585B30807C2A87:2011-08-18:::-: # Paul Tagliamonte ::scESC: # without the newline parsed_fingerprint = line.split(":") current_uid = parsed_fingerprint[9] next_line_contains_fpr = True if len(possible_fingerprints) > 1: raise DmCommandError("DM argument `%s' is ambiguous. " "Possible choices:\n%s" % (args.dm, pretty_print_list(possible_fingerprints))) possible_fingerprints = possible_fingerprints[0] logger.info("Picking DM %s with fingerprint %s" % possible_fingerprints) args.dm = possible_fingerprints[1] def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/contrib/0000755000000000000000000000000012241261643014043 5ustar dput-ng-1.7/dput/commands/contrib/__init__.py0000644000000000000000000000000012160717773016154 0ustar dput-ng-1.7/dput/commands/contrib/debomatic.py0000644000000000000000000001777312203760156016364 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2013 Luca Falavigna # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.commands.cancel import generate_debianqueued_commands_name from dput.profile import load_profile class DebomaticCommandError(DcutError): pass class BuilddepCommand(AbstractCommand): def __init__(self, interface): super(BuilddepCommand, self).__init__(interface) self.cmd_name = "debomatic-builddep" self.cmd_purpose = ("rebuild a source package with Deb-o-Matic adding " "specific build-dependencies") def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-s', '--source', metavar="SOURCE", action='store', default=None, help="source pacakge to rebuild. ", required=True) parser.add_argument('-v', '--version', metavar="VERSION", action='store', default=None, help="version of " "the source package to rebuild. ", required=True) parser.add_argument('-d', '--distribution', metavar="DISTRIBUTION", action='store', default=None, help="distribution " "which rebuild the package for. ", required=True) parser.add_argument('-p', '--packages', metavar="PACKAGES", action='store', default=None, help="packages to " "be installed at compile time. ", required=True) def produce(self, fh, args): fh.write("Commands:\n") fh.write(" builddep %s_%s %s %s\n" % (args.source, args.version, args.distribution, args.packages)) def validate(self, args): profile = load_profile(args.host) if (not 'allow_debomatic_commands' in profile or not profile['allow_debomatic_commands']): raise DebomaticCommandError( "Deb-o-Matic commands not supported for this profile" ) def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) class PorterCommand(AbstractCommand): def __init__(self, interface): super(PorterCommand, self).__init__(interface) self.cmd_name = "debomatic-porter" self.cmd_purpose = "generate a porter upload with Deb-o-Matic" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-s', '--source', metavar="SOURCE", action='store', default=None, help="source pacakge to generate a " "porter upload for. ", required=True) parser.add_argument('-v', '--version', metavar="VERSION", action='store', default=None, help="version of " "the source package to generate a porter upload " "for. ", required=True) parser.add_argument('-d', '--distribution', metavar="DISTRIBUTION", action='store', default=None, help="distribution " "which build the package for. ", required=True) parser.add_argument('-m', '--maintainer', metavar="MAINTAINER", action='store', default=None, help="contact to be " "listed in the Maintainer field. ", required=True) def produce(self, fh, args): fh.write("Commands:\n") fh.write(" porter %s_%s %s %s\n" % (args.source, args.version, args.distribution, args.maintainer)) def validate(self, args): profile = load_profile(args.host) if (not 'allow_debomatic_commands' in profile or not profile['allow_debomatic_commands']): raise DebomaticCommandError( "Deb-o-Matic commands not supported for this profile" ) def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) class RebuildCommand(AbstractCommand): def __init__(self, interface): super(RebuildCommand, self).__init__(interface) self.cmd_name = "debomatic-rebuild" self.cmd_purpose = "rebuild a source package with Deb-o-Matic" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-s', '--source', metavar="SOURCE", action='store', default=None, help="source pacakge to rebuild. ", required=True) parser.add_argument('-v', '--version', metavar="VERSION", action='store', default=None, help="version of " "the source package to rebuild. ", required=True) parser.add_argument('-d', '--distribution', metavar="DISTRIBUTION", action='store', default=None, help="distribution " "which rebuild the package for. ", required=True) parser.add_argument('-o', '--origin', metavar="ORIGIN", action='store', default='', help="distribution to pick source " "package from. ") def produce(self, fh, args): fh.write("Commands:\n") fh.write(" rebuild %s_%s %s %s\n" % (args.source, args.version, args.distribution, args.origin)) def validate(self, args): profile = load_profile(args.host) if (not 'allow_debomatic_commands' in profile or not profile['allow_debomatic_commands']): raise DebomaticCommandError( "Deb-o-Matic commands not supported for this profile" ) def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) class RmCommand(AbstractCommand): def __init__(self, interface): super(RmCommand, self).__init__(interface) self.cmd_name = "debomatic-rm" self.cmd_purpose = "remove a file from Deb-o-Matic upload queue" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-f', '--file', metavar="FILENAME", action='store', default=None, help="file to be removed. " "The argument could contain Unix shell patterns.", nargs="+", required=True) def produce(self, fh, args): fh.write("Commands:\n") for rm_file in args.file: fh.write(" rm %s\n" % rm_file) def validate(self, args): profile = load_profile(args.host) if (not 'allow_debomatic_commands' in profile or not profile['allow_debomatic_commands']): raise DebomaticCommandError( "Deb-o-Matic commands not supported for this profile" ) def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/reschedule.py0000644000000000000000000000440312114414240015071 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.commands.cancel import generate_debianqueued_commands_name class RescheduleCommandError(DcutError): pass class RescheduleCommand(AbstractCommand): def __init__(self, interface): super(RescheduleCommand, self).__init__(interface) self.cmd_name = "reschedule" self.cmd_purpose = "reschedule a deferred upload" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-f', '--file', metavar="FILENAME", action='store', default=None, help="file name to be rescheduled", nargs=1, required=True) parser.add_argument('-d', '--days', metavar="DAYS", action='store', default=None, help="reschedule for DAYS days." " Takes an argument from 0 to 15", type=int, choices=range(0, 16), required=True) def produce(self, fh, args): fh.write("Commands:\n") for rm_file in args.file: fh.write(" %s %s %s-day\n" % ( self.cmd_name, rm_file, args.days )) def validate(self, args): # TODO: any todos here? pass def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/rm.py0000644000000000000000000000635012114414240013367 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.commands.cancel import generate_debianqueued_commands_name from dput.changes import Changes from dput.core import logger # XXX: Generate rm from .changes? class RmCommandError(DcutError): pass class RmCommand(AbstractCommand): def __init__(self, interface): super(RmCommand, self).__init__(interface) self.cmd_name = "rm" self.cmd_purpose = "remove a file from the upload queue" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-f', '--file', metavar="FILENAME", action='store', default=None, help="file to be removed. " "If the argument is a CHANGES file, a rm command " "for all .deb packages in it is created", nargs="+", required=True) parser.add_argument('--searchdirs', action='store_true', default=None, help="Search in all directories for the given" " file. Only supported for files in the DELAYED" " queue.") def produce(self, fh, args): fh.write("Commands:\n") for rm_file in args.file: fh.write(" %s %s %s\n" % ( self.cmd_name, "--searchdirs" if args.searchdirs else "", rm_file )) def validate(self, args): # TODO: argument can be either a path or a base name, but then the user # most likely wants to add --searchdirs file_list = [] if args.file: for argument in args.file: if argument.endswith("changes"): # force searchdirs args.searchdirs = True changes_file = Changes(filename=argument) file_list += changes_file.get_files() file_list.append(changes_file.get_filename()) logger.info("Expanding package list for removals to: %s" % reduce(lambda x, xs: xs + ", " + x, file_list)) args.file = file_list else: raise RmCommandError("No file to be removed supplied?") def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/upload.py0000644000000000000000000000306312114414240014233 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. from dput.command import AbstractCommand class UploadCommand(AbstractCommand): def __init__(self, interface): super(UploadCommand, self).__init__(interface) self.cmd_name = "upload" self.cmd_purpose = "Upload an existing file as is" def generate_commands_name(self, profile): pass def register(self, parser, **kwargs): parser.add_argument('-f', '--file', metavar="FILENAME", action='store', dest='upload_file', default=None, required=True, help="file name to be uploaded") def produce(self, fh, args): return def validate(self, args): return def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/commands/cancel.py0000644000000000000000000000454012160717773014217 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. import time from dput.command import AbstractCommand from dput.exceptions import DcutError from dput.core import logger, get_local_username class CancelCommandError(DcutError): pass def generate_debianqueued_commands_name(profile): # for debianqueued: $login-$timestamp.commands # for dak: $login-$timestamp.dak-commands the_file = "%s-%s.commands" % (get_local_username(), int(time.time())) logger.trace("Commands file will be named %s" % (the_file)) return the_file class CancelCommand(AbstractCommand): def __init__(self, interface): super(CancelCommand, self).__init__(interface) self.cmd_name = "cancel" self.cmd_purpose = "cancel a deferred upload" def generate_commands_name(self, profile): return generate_debianqueued_commands_name(profile) def register(self, parser, **kwargs): parser.add_argument('-f', '--file', metavar="FILENAME", action='store', default=None, help="file name to be removed", nargs="+", required=True) def produce(self, fh, args): fh.write("Commands:\n") for rm_file in args.file: fh.write(" %s %s\n" % ( self.cmd_name, rm_file )) def validate(self, args): # TODO: Validate input. It must be a changes file reference # Aside we cannot do much. The file is remote, so we cannot # process it pass def name_and_purpose(self): return (self.cmd_name, self.cmd_purpose) dput-ng-1.7/dput/util.py0000644000000000000000000002464312142021413012127 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Misc & helper functions """ import os import json import shlex import importlib import subprocess from contextlib import contextmanager import dput.core from dput.core import logger from dput.exceptions import (NoSuchConfigError, DputConfigurationError, InvalidConfigError) def load_obj(obj_path): """ Dynamically load an object (class, method, etc) by name (such as `dput.core.ClassName`), and return that object to work with. This is useful for loading modules on the fly, without them being all loaded at once, or even in the same package. Call this routine with at least one dot in it -- it attempts to load the module (such as dput.core) and use getattr to load the thing - similar to how `from` works. """ dput.core.mangle_sys() logger.trace("Loading object: %s" % (obj_path)) module, obj = obj_path.rsplit(".", 1) mod = importlib.import_module(module) fltr = getattr(mod, obj) return fltr def get_obj(cls, checker_method): # checker_method is a bad name. """ Get an object by plugin def (``checker_method``) in class ``cls`` (such as ``hooks``). """ logger.trace("Attempting to resolve %s %s" % (cls, checker_method)) try: config = load_config(cls, checker_method) validate_object('plugin', config, "%s/%s" % (cls, checker_method)) if config is None or config == {}: raise NoSuchConfigError("No such config") except NoSuchConfigError: logger.debug("failed to resolve config %s" % (checker_method)) return None path = config['path'] logger.trace("loading %s %s" % (cls, path)) try: return load_obj(path) except ImportError as e: logger.warning("failed to resolve path %s: %s" % (path, e)) return None def run_command(command): """ Run a synchronized command. The argument must be a list of arguments. Returns a triple (stdout, stderr, exit_status) If there was a problem to start the supplied command, (None, None, -1) is returned """ if not isinstance(command, list): command = shlex.split(command) try: pipe = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError as e: logger.error("Could not execute %s: %s" % (" ".join(command), e)) return (None, None, -1) (output, stderr) = pipe.communicate() #if pipe.returncode != 0: # error("Command %s returned failure: %s" % (" ".join(command), stderr)) return (output, stderr, pipe.returncode) def get_configs(cls): """ Get all valid config targets for class ``cls``. """ configs = set() for path in dput.core.CONFIG_LOCATIONS: path = "%s/%s" % (path, cls) if os.path.exists(path): for fil in os.listdir(path): xtn = ".json" if fil.endswith(xtn): configs.add(fil[:-len(xtn)]) return configs def _config_cleanup(obj): """ Handle merging plus, minus and set fields. Internal only. """ def do_add(new, old): if not isinstance(new, list) and not isinstance(old, list): raise Exception("WTF NOT LIST") # XXX: better exception nset = set(new) oset = set(old) nobj = oset | nset return list(nobj) def do_sub(new, old): if not isinstance(new, list) and not isinstance(old, list): raise Exception("WTF NOT LIST") # XXX: better exception nset = set(new) oset = set(old) nobj = oset - nset return list(nobj) def do_eql(new, old): return new operators = { "+": do_add, "-": do_sub, "=": do_eql } ret = obj.copy() for key in obj: operator = key[0] if operator not in operators: continue kname = key[1:] op = operators[operator] if kname in ret: ret[kname] = op(ret[key], ret[kname]) else: foo = op(ret[key], []) if foo != []: ret[kname] = foo ret.pop(key) return ret def validate_object(schema, obj, name): sobj = None for root in dput.core.SCHEMA_DIRS: if sobj is not None: logger.debug("Skipping %s" % (root)) continue logger.debug("Loading schema %s from %s" % (schema, root)) spath = "%s/%s.json" % ( root, schema ) try: if os.path.exists(spath): sobj = json.load(open(spath, 'r')) else: logger.debug("No such config: %s" % (spath)) except ValueError as e: raise DputConfigurationError("syntax error in %s: %s" % ( spath, e )) if sobj is None: logger.critical("Schema not found: %s" % (schema)) raise DputConfigurationError("No such schema: %s" % (schema)) try: import validictory validictory.validate(obj, sobj) except ImportError: pass except validictory.validator.ValidationError as e: err = str(e) error = "Error with config file %s - %s" % ( name, err ) ex = InvalidConfigError(error) ex.obj = obj ex.root = e ex.config_name = name ex.sdir = dput.core.SCHEMA_DIRS ex.schema = schema raise ex def load_config(config_class, config_name, default=None, configs=None, config_cleanup=True): """ Load any dput configuration given a ``config_class`` (such as ``hooks``), and a ``config_name`` (such as ``lintian`` or ``tweet``). Optional kwargs: ``default`` is a default to return, in case the config file isn't found. If this isn't provided, this function will raise a :class:`dput.exceptions.NoSuchConfigError`. ``configs`` is a list of config files to check. When this isn't provided, we check dput.core.CONFIG_LOCATIONS. """ logger.debug("Loading configuration: %s %s" % ( config_class, config_name )) roots = [] ret = {} found = False template_path = "%s/%s/%s.json" locations = configs or dput.core.CONFIG_LOCATIONS for config in locations: logger.trace("Checking for configuration: %s" % (config)) path = template_path % ( config, config_class, config_name ) logger.trace("Checking - %s" % (path)) try: if os.path.exists(path): found = True roots.append(path) ret.update(json.load(open(path, 'r'))) except ValueError as e: raise DputConfigurationError("syntax error in %s: %s" % ( path, e )) if not found: if default is not None: return default raise NoSuchConfigError("No such config: %s/%s" % ( config_class, config_name )) if 'meta' in ret and ( config_class != 'metas' or ret['meta'] != config_name ): metainfo = load_config( "metas", ret['meta'], default={} ) # configs=configs) # Erm, is this right? For some reason, I don't think it is. Meta # handling is a hemorrhoid in my ass. Fuck it, it works. Ship it. # -- PRT for key in metainfo: if not key in ret: ret[key] = metainfo[key] else: logger.trace("Ignoring key %s for %s (%s)" % ( key, ret['meta'], metainfo[key] )) obj = ret if config_cleanup: obj = _config_cleanup(ret) if obj != {}: return obj if default is not None: return default logger.debug("Failed to load configuration %s" % (config_name)) nsce = NoSuchConfigError("No such configuration: '%s' in class '%s'" % ( config_name, config_class )) nsce.config_class = config_class nsce.config_name = config_name nsce.checked = dput.core.CONFIG_LOCATIONS raise nsce def obj_docs(cls, ostr): """ Get an object's docstring by name / class def. """ obj = get_obj(cls, ostr) if obj is None: raise DputConfigurationError("No such object: `%s'" % ( ostr )) return obj.__doc__ @contextmanager def get_obj_by_name(cls, name, profile): """ Run a function, defined by ``name``, filed in class ``cls`` """ logger.trace("running %s: %s" % (cls, name)) obj = get_obj(cls, name) if obj is None: raise DputConfigurationError("No such obj: `%s'" % ( name )) interface = 'cli' if 'interface' in profile: interface = profile['interface'] logger.trace("Using interface %s" % (interface)) interface_obj = get_obj('interfaces', interface) if interface_obj is None: raise DputConfigurationError("No such interface: `%s'" % ( interface )) interface = interface_obj() interface.initialize() try: yield (obj, interface) finally: pass interface.shutdown() def run_func_by_name(cls, name, changes, profile): """ Run a function, defined by ``name``, filed in class ``cls``, with a :class:`dput.changes.Changes` (``changes``), and profile ``profile``. This is used to run the hooks, internally. """ with get_obj_by_name(cls, name, profile) as(obj, interface): obj(changes, profile, interface) dput-ng-1.7/dput/changes.py0000644000000000000000000002632312224362400012564 0ustar # -*- coding: utf-8 -*- # # changes.py — .changes file handling class # # This file was originally part of debexpo # https://alioth.debian.org/projects/debexpo/ # # Copyright © 2008 Jonny Lamb # Copyright © 2010 Jan Dittberner # Copyright © 2012 Arno Töll # Copyright © 2012 Paul Tagliamonte # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to use, # copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following # conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. """ This code deals with the reading and processing of Debian .changes files. This code is copyright (c) Jonny Lamb, and is used by dput, rather then created as a result of it. Thank you Jonny. """ __author__ = 'Jonny Lamb' __copyright__ = 'Copyright © 2008 Jonny Lamb, Copyright © 2010 Jan Dittberner' __license__ = 'MIT' import sys import os.path import hashlib from debian import deb822 from dput.core import logger from dput.util import run_command from dput.exceptions import ChangesFileException class Changes(object): """ Changes object to help process and store information regarding Debian .changes files, used in the upload process. """ def __init__(self, filename=None, string=None): """ Object constructor. The object allows the user to specify **either**: #. a path to a *changes* file to parse #. a string with the *changes* file contents. :: a = Changes(filename='/tmp/packagename_version.changes') b = Changes(string='Source: packagename\\nMaintainer: ...') ``filename`` Path to *changes* file to parse. ``string`` *changes* file in a string to parse. """ if (filename and string) or (not filename and not string): raise TypeError if filename: self._absfile = os.path.abspath(filename) self._data = deb822.Changes(open(filename)) else: self._data = deb822.Changes(string) if len(self._data) == 0: raise ChangesFileException('Changes file could not be parsed.') if filename: self.basename = os.path.basename(filename) else: self.basename = None self._directory = "" self.is_python3 = False if sys.version_info[0] >= 3: self.is_python3 = True def get_filename(self): """ Returns the filename from which the changes file was generated from. Please do note this is just the basename, not the entire full path, or even a relative path. For the absolute path to the changes file, please see :meth:`get_changes_file`. """ return self.basename def get_changes_file(self): """ Return the full, absolute path to the changes file. For just the filename, please see :meth:`get_filename`. """ return os.path.join(self._directory, self.get_filename()) def get_files(self): """ Returns a list of files referenced in the changes file, such as the .dsc, .deb(s), .orig.tar.gz, and .diff.gz or .debian.tar.gz. All strings in the array will be absolute paths to the files. """ return [os.path.join(self._directory, z['name']) for z in self._data['Files']] def __getitem__(self, key): """ Returns the value of the rfc822 key specified. ``key`` Key of data to request. """ return self._data[key] def __contains__(self, key): """ Returns whether the specified RFC822 key exists. ``key`` Key of data to check for existence. """ return key in self._data def get(self, key, default=None): """ Returns the value of the rfc822 key specified, but defaults to a specific value if not found in the rfc822 file. ``key`` Key of data to request. ``default`` Default return value if ``key`` does not exist. """ return self._data.get(key, default) def get_component(self): """ Returns the component of the package. """ return self._parse_section(self._data['Files'][0]['section'])[0] def get_priority(self): """ Returns the priority of the package. """ return self._parse_section(self._data['Files'][0]['priority'])[1] def get_section(self): """ Returns the section of the package. """ return self._parse_section(self._data['Files'][0]['section'])[1] def get_dsc(self): """ Returns the name of the .dsc file. """ for item in self.get_files(): if item.endswith('.dsc'): return item def get_diff(self): """ Returns the name of the .diff.gz file if there is one, otherwise None. """ for item in self.get_files(): if item.endswith('.diff.gz') or item.endswith('.debian.tar.gz'): return item return None def get_pool_path(self): """ Returns the path the changes file would be """ return self._data.get_pool_path() def get_package_name(self): """ Returns the source package name """ return self.get("Source") def _parse_section(self, section): """ Works out the component and section from the "Section" field. Sections like `python` or `libdevel` are in main. Sections with a prefix, separated with a forward-slash also show the component. It returns a list of strings in the form [component, section]. For example, `non-free/python` has component `non-free` and section `python`. ``section`` Section name to parse. """ if '/' in section: return section.split('/') else: return ['main', section] def set_directory(self, directory): if directory: self._directory = directory else: self._directory = "" def validate(self, check_hash="sha1", check_signature=True): """ See :meth:`validate_checksums` for ``check_hash``, and :meth:`validate_signature` if ``check_signature`` is True. """ self.validate_checksums(check_hash) if check_signature: self.validate_signature(check_signature) else: logger.info("Not checking signature") def validate_signature(self, check_signature=True): """ Validate the GPG signature of a .changes file. Throws a :class:`dput.exceptions.ChangesFileException` if there's an issue with the GPG signature. Returns the GPG key ID. """ gpg_path = "gpg" (gpg_output, gpg_output_stderr, exit_status) = run_command([ gpg_path, "--status-fd", "1", "--verify", "--batch", self.get_changes_file() ]) if exit_status == -1: raise ChangesFileException( "Unknown problem while verifying signature") # contains verbose human readable GPG information if self.is_python3: gpg_output_stderr = str(gpg_output_stderr, encoding='utf8') print(gpg_output_stderr) if self.is_python3: gpg_output = gpg_output.decode(encoding='UTF-8') if gpg_output.count('[GNUPG:] GOODSIG'): pass elif gpg_output.count('[GNUPG:] BADSIG'): raise ChangesFileException("Bad signature") elif gpg_output.count('[GNUPG:] ERRSIG'): raise ChangesFileException("Error verifying signature") elif gpg_output.count('[GNUPG:] NODATA'): raise ChangesFileException("No signature on") else: raise ChangesFileException( "Unknown problem while verifying signature" ) key = None for line in gpg_output.split("\n"): if line.startswith('[GNUPG:] VALIDSIG'): key = line.split()[2] return key def validate_checksums(self, check_hash="sha1"): """ Validate checksums for a package, using ``check_hack``'s type to validate the package. Valid ``check_hash`` types: * sha1 * sha256 * md5 * md5sum """ logger.debug("validating %s checksums" % (check_hash)) for filename in self.get_files(): if check_hash == "sha1": hash_type = hashlib.sha1() checksums = self.get("Checksums-Sha1") field_name = "sha1" elif check_hash == "sha256": hash_type = hashlib.sha256() checksums = self.get("Checksums-Sha256") field_name = "sha256" elif check_hash == "md5": hash_type = hashlib.md5() checksums = self.get("Files") field_name = "md5sum" for changed_files in checksums: if changed_files['name'] == os.path.basename(filename): break else: assert( "get_files() returns different files than Files: knows?!") with open(filename, "rb") as fc: while True: chunk = fc.read(131072) if not chunk: break hash_type.update(chunk) fc.close() if not hash_type.hexdigest() == changed_files[field_name]: raise ChangesFileException( "Checksum mismatch for file %s: %s != %s" % ( filename, hash_type.hexdigest(), changed_files[field_name] )) else: logger.trace("%s Checksum for file %s matches" % ( field_name, filename )) def parse_changes_file(filename, directory=None): """ Parse a .changes file and return a dput.changes.Change instance with parsed changes file data. The optional directory argument refers to the base directory where the referred files from the changes file are expected to be located. """ _c = Changes(filename=filename) _c.set_directory(directory) return(_c) dput-ng-1.7/dput/hook.py0000644000000000000000000000526012160717773012131 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Implementation of the interface to run a hook. """ from dput.util import obj_docs, run_func_by_name, load_config, validate_object from dput.core import logger try: # Optional clojure-py integration. import clojure.main # NOQA except ImportError: logger.trace("No clojure support :(") try: # Optional hy integration. import hy # NOQA except ImportError: logger.trace("No hython support :(") def hook_docs(hook): return obj_docs('hooks', hook) def get_hooks(profile): for hook in profile['hooks']: conf = load_config('hooks', hook) validate_object('plugin', conf, 'hooks/%s' % (hook)) yield (hook, conf) def run_pre_hooks(changes, profile): for name, hook in get_hooks(profile): if 'pre' in hook and hook['pre']: run_hook(name, hook, changes, profile) if 'pre' not in hook and 'post' not in hook: logger.warning("Hook: %s has no pre/post ordering. Assuming " "pre.") run_hook(name, hook, changes, profile) def run_post_hooks(changes, profile): for name, hook in get_hooks(profile): if 'post' in hook and hook['post']: run_hook(name, hook, changes, profile) def run_hook(name, hook, changes, profile): """ Run a hook (by the name of ``hook``) against the changes file (by the name of ``changes``), with the upload profile (named ``profile``). args: ``hook`` (str) string of the hook (which is the name of the JSON file which contains the hook def) ``changes`` (:class:`dput.changes.Changes`) changes file that the hook should be run against. ``profile`` (dict) dictionary of the profile that will help guide the hook's runtime. """ logger.info("running %s: %s" % (name, hook['description'])) return run_func_by_name('hooks', name, changes, profile) dput-ng-1.7/dput/interfaces/0000755000000000000000000000000012241261643012725 5ustar dput-ng-1.7/dput/interfaces/__init__.py0000644000000000000000000000146512114414240015034 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. dput-ng-1.7/dput/interfaces/cli.py0000644000000000000000000001147612114414240014047 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ CLI User Interface Implementation """ import sys import getpass from dput.core import logger from dput.interface import (AbstractInterface, ALL_BUTTONS, BUTTON_YES_NO, BUTTON_OK, BUTTON_YES) class CLInterface(AbstractInterface): """ Concrete implementation of the command line user interface. """ def initialize(self, **kwargs): """ See :meth:`dput.interface.AbstractInterface.initialize` """ pass # nothing here. def button_to_str(self, button): """ Translate a button name to it's label value. """ for item in ALL_BUTTONS: if item == button: return item assert(False) def str_to_button(self, str_button, default): """ Translate a string input to a button known to the interface. In case of the CLI interface there is no straight notion of a button, so this is abstracted by expected data input treated as button. This method guesses based on the supplied argument and the supplied default value, which button the user meant in 'Do you wanna foo [y/N]?' situations. """ str_button = str_button.lower() # return default when no input was supplied if default and not str_button: return default # compare literally if str_button in ALL_BUTTONS: return str_button # guess input button until only one choice is left or the outcome is # known to be ambiguous for index in range(0, len(str_button) + 1): buttons = [count for count in ALL_BUTTONS if count.startswith(str_button[0:index])] if len(buttons) == 0: break elif len(buttons) == 1: return buttons[0] return None def boolean(self, title, message, question_type=BUTTON_YES_NO, default=None): """ See :meth:`dput.interface.AbstractInterface.boolean` """ super(CLInterface, self).boolean(title, message, question_type) choices = "" question_len = len(question_type) for question in question_type: button_name = self.button_to_str(question) if question == default: button_name = button_name.upper() choices += button_name question_len -= 1 if question_len: choices += ", " user_input = None while not user_input: user_input = self.question(title, "%s [%s]" % (message, choices)) user_input = self.str_to_button(user_input, default) logger.trace("translated user input '%s'" % (user_input)) if user_input in (BUTTON_OK, BUTTON_YES): return True return False def message(self, title, message, question_type=BUTTON_OK): """ See :meth:`dput.interface.AbstractInterface.message` """ super(CLInterface, self).message(title, message, question_type) if title: sys.stdout.write("%s: " % (title)) sys.stdout.write("%s\n" % (message)) def list(self, title, message, selections=[]): """ See :meth:`dput.interface.AbstractInterface.list` """ super(CLInterface, self).list(title, message, selections) # XXX implement when needed. No use so far raise NotImplemented() def question(self, title, message, echo_input=True): """ See :meth:`dput.interface.AbstractInterface.question` """ super(CLInterface, self).question(title, message, echo_input) message = "%s: " % (message) if title: sys.stdout.write("%s: " % (title)) if echo_input: sys.stdout.write(message) return sys.stdin.readline().strip() else: return getpass.getpass(message) def shutdown(self): """ See :meth:`dput.interface.AbstractInterface.shutdown` """ pass # nothing here. dput-ng-1.7/dput/exceptions.py0000644000000000000000000000476312114414240013337 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Base exceptions. All hooks and internal modules should subclass Exceptions listed here. """ class DputError(BaseException): """ Most basic dput error. All other Exceptions must inherit from this when it is sensable to do so. """ pass class DcutError(BaseException): pass class DputConfigurationError(DputError): """ Errors in the parsing or retrieving of configuration files should raise an instance of this, or a subclass thereof. """ pass class NoSuchConfigError(DputError): """ Thrown when dput can not find a Configuration file or block that is requested. """ pass class InvalidConfigError(DputError): """ Config file was loaded properly, but it was missing part of it's required fields. """ pass class ChangesFileException(DputError): """ Thrown when there's an error processing / verifying a .changes file (most often via the :class:`dput.changes.Changes` object) """ pass class DscFileException(DputError): """ Thrown when there's an error processing / verifying a .dsc file (most often via the :class:`dput.changes.Dsc` object) """ pass class UploadException(DputError): """ Thrown when there's an error uploading, or creating an uploader. Usually thrown by a subclass of the :class:`dput.uploader.AbstractUploader` """ pass class HookException(DputError): """ Thrown when there's an error checking, or creating a checker. Usually thrown by a checker invoked by :class:`dput.checker.run_hooks`. """ pass class NoSuchHostError(DputError): """ Thrown when the network doesn't allow us to connect to a host. """ pass dput-ng-1.7/dput/command.py0000644000000000000000000001765012160717773012615 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # XXX: DOCUMENT ME. import abc import os import tempfile import email.utils import shutil import pwd import socket import dput.profile from dput.util import get_obj_by_name, get_configs, run_command from dput.core import logger, get_local_username from dput.exceptions import (UploadException, DputConfigurationError, DcutError, NoSuchConfigError) from dput.overrides import force_passive_ftp_upload from dput.uploader import uploader class AbstractCommand(object): """ Abstract base class for all concrete dcut command implementations. """ __metaclass__ = abc.ABCMeta def __init__(self, interface): self.cmd_name = None self.cmd_purpose = None self.interface = interface @abc.abstractmethod def register(self, parser, **kwargs): pass @abc.abstractmethod def produce(self, fh, args): pass @abc.abstractmethod def validate(self, args): pass @abc.abstractmethod def name_and_purpose(self): pass @abc.abstractmethod def generate_commands_name(self, profile): pass def find_commands(): return get_configs('commands') def load_commands(profile): commands = [] for command in profile['valid_commands']: logger.debug("importing command: %s" % (command)) try: # XXX: Stubbed the profile for now. That ignores any user choice # on the profile. # Reason being that the profile and the argument parser is a # transitive circular dependency. That should be fixed at some # point. with get_obj_by_name('commands', command, {}) as(obj, interface): commands.append(obj(interface)) except NoSuchConfigError: raise DputConfigurationError("No such command: `%s'" % (command)) return commands def write_header(fh, profile, args): email_address = os.environ.get("DEBEMAIL", None) if email_address is None: email_address = os.environ.get("EMAIL", None) name = os.environ.get("DEBFULLNAME", None) if not name: pwd_entry = pwd.getpwnam(get_local_username()) gecos_name = pwd_entry.pw_gecos.split(",", 1) if len(gecos_name) > 1: name = gecos_name[0] else: name = pwd_entry.pw_gecos if not email_address: email_address = socket.getfqdn(socket.gethostname()) if args.maintainer: (name, email_address) = email.utils.parseaddr(args.maintainer) logger.debug("Using %s <%s> as uploader identity" % (name, email_address)) if not name or not email_address: raise DcutError("Your name or email could not be retrieved." "Please set DEBEMAIL and DEBFULLNAME or provide" " a full identity through --maintainer") fh.write("Archive: %s\n" % (profile['fqdn'])) if name and email_address: fh.write("Uploader: %s <%s>\n" % (name, email_address)) return (name, email_address) def sign_file(filename, keyid=None, profile=None, name=None, email=None): logger.debug("Signing file %s - signature hints are key: %s, " "name: %s, email: %s" % (filename, keyid, name, email)) gpg_path = "gpg" if keyid: identity_hint = keyid else: # hard to see here, but name and email is guaranteed to be set in # write_header() if name: identity_hint = name if email: identity_hint += " <%s>" % (email) logger.trace("GPG identity hint: %s" % (identity_hint)) (gpg_output, gpg_output_stderr, exit_status) = run_command([ gpg_path, "--default-key", identity_hint, "--status-fd", "1", "--sign", "--armor", "--clearsign", filename ]) if exit_status == -1: raise DcutError("Unknown problem while making cleartext signature") if exit_status != 0: raise DcutError("Failed to make cleartext signature " "to commands file:\n%s" % (gpg_output_stderr)) if gpg_output.count('[GNUPG:] SIG_CREATED'): pass else: raise DcutError("Failed to make cleartext signature:\n%s" % (gpg_output_stderr)) os.unlink(filename) shutil.move("%s.asc" % (filename), filename) def upload_commands_file(filename, upload_filename, profile, args): with uploader(profile['method'], profile, simulate=args.simulate) as obj: logger.info("Uploading %s to %s" % ( upload_filename, profile['name'] )) obj.upload_file(filename, upload_filename=upload_filename) def invoke_dcut(args): profile = dput.profile.load_profile(args.host) fqdn = None if 'fqdn' in profile: fqdn = profile['fqdn'] if not 'allow_dcut' in profile or not profile['allow_dcut']: raise UploadException("Profile %s does not allow command file uploads" "Please set allow_dcut=1 to allow such uploads") logger.info("Uploading commands file to %s (incoming: %s)" % ( fqdn or profile['name'], profile['incoming'] )) if args.simulate: logger.warning("Not uploading for real - dry run") command = args.command assert(issubclass(type(command), AbstractCommand)) command.validate(args) if args.passive: force_passive_ftp_upload(profile) upload_path = None fh = None upload_filename = command.generate_commands_name(profile) try: if command.cmd_name == "upload": logger.debug("Uploading file %s as is to %s" % (args.upload_file, profile['name'])) if not os.access(args.upload_file, os.R_OK): raise DcutError("Cannot access %s: No such file" % ( args.upload_file )) upload_path = args.upload_file else: fh = tempfile.NamedTemporaryFile(mode='w+r', delete=False) (name, email) = write_header(fh, profile, args) command.produce(fh, args) fh.flush() #print fh.name fh.close() signing_key = None if "default_keyid" in profile: signing_key = profile["default_keyid"] if args.keyid: signing_key = args.keyid sign_file(fh.name, signing_key, profile, name, email) upload_path = fh.name if not args.simulate and not args.output: upload_commands_file(upload_path, upload_filename, profile, args) elif args.output and not args.simulate: if os.access(args.output, os.R_OK): logger.error("Not writing %s: File already exists" % ( args.output )) # ... but intentionally do nothing # TODO: or raise exception? return shutil.move(fh.name, args.output) elif args.simulate: pass else: # we should *never* come here assert(False) finally: if fh and os.access(fh.name, os.R_OK): os.unlink(fh.name) dput-ng-1.7/dput/__init__.py0000644000000000000000000000233112114414240012702 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. __license__ = "GPL-2+" __appname__ = "dput" __authors__ = [ "Arno Töll ", "Paul Tagliamonte " ] from dput.uploader import invoke_dput_simple as upload from dput.uploader import invoke_dput as upload_package # NOQA """ See :func:`dput.uploader.invoke_dput`. """ from dput.command import invoke_dcut as upload_command # NOQA """ See :func:`dput.command.invoke_dcut`. """ dput-ng-1.7/dput/dsc.py0000644000000000000000000000650412114414240011722 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ This code deals with the reading and processing of Debian .dsc files. """ import os import os.path from debian import deb822 from dput.exceptions import DscFileException class Dsc(object): """ Changes object to help process and store information regarding Debian .dsc files, used in the upload process. """ def __init__(self, filename=None, string=None): """ Object constructor. The object allows the user to specify **either**: #. a path to a *changes* file to parse #. a string with the *changes* file contents. :: a = Dsc(filename='/tmp/packagename_version.changes') b = Dsc(string='Source: packagename\\nMaintainer: ...') ``filename`` Path to *changes* file to parse. ``string`` *dsc* file in a string to parse. """ if (filename and string) or (not filename and not string): raise TypeError if filename: self._absfile = os.path.abspath(filename) self._data = deb822.Dsc(file(filename)) else: self._data = deb822.Dsc(string) if len(self._data) == 0: raise DscFileException('Changes file could not be parsed.') if filename: self.basename = os.path.basename(filename) else: self.basename = None self._directory = "" def __getitem__(self, key): """ Returns the value of the rfc822 key specified. ``key`` Key of data to request. """ return self._data[key] def __contains__(self, key): """ Returns whether the specified RFC822 key exists. ``key`` Key of data to check for existence. """ return key in self._data def get(self, key, default=None): """ Returns the value of the rfc822 key specified, but defaults to a specific value if not found in the rfc822 file. ``key`` Key of data to request. ``default`` Default return value if ``key`` does not exist. """ return self._data.get(key, default) def parse_dsc_file(filename, directory=None): """ Parse a .dsc file and return a dput.changes.Dsc instance with parsed changes file data. The optional directory argument refers to the base directory where the referred files from the changes file are expected to be located. XXX: The directory argument is ignored """ _c = Dsc(filename=filename) return(_c) dput-ng-1.7/dput/interface.py0000644000000000000000000000674212121625643013126 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Interface implementation. """ import abc """ A button labeled 'yes' """ BUTTON_YES = "yes" """ A button labeled 'no' """ BUTTON_NO = "no" """ A button labeled 'cancel' """ BUTTON_CANCEL = "cancel" """ A button labeled 'ok' """ BUTTON_OK = "ok" (WIDGET_BOOLEAN, WIDGET_MESSAGE, WIDGET_LIST, WIDGET_QUESTION) = range(4) # some "shortcuts" ALL_BUTTONS = [BUTTON_YES, BUTTON_NO, BUTTON_CANCEL, BUTTON_OK] BUTTON_YES_NO = [BUTTON_YES, BUTTON_NO] BUTTON_OK_CANCEL = [BUTTON_OK, BUTTON_CANCEL] class AbstractInterface(object): """ Abstract base class for Concrete implementations of user interfaces. The invoking process will instantiate the process, call initialize, query (any number of times), and shutdown. """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def initialize(self, **kwargs): """ Set up the interface state. """ pass def boolean(self, title, message, question_type=BUTTON_YES_NO, default=None): """ Display a question returning a boolean value. This is evaluated by checking the button return code either to be BUTTON_YES or BUTTON_OK """ self.widget_type = WIDGET_BOOLEAN self.message = message self.question_type = question_type self.default = default def message(self, title, message, question_type=BUTTON_OK): """ Display a message and a confirmation button when required by the interface to make sure the user noticed the message Some interfaces, e.g. the CLI may ignore the button. """ self.widget_type = WIDGET_MESSAGE self.message = message self.question_type = question_type def list(self, title, message, selections=[]): """ Display a list of alternatives the user can choose from, returns a list of selections. """ self.widget_type = WIDGET_LIST self.message = message self.selection = selections def question(self, title, message, echo_input=True): """ Query for user input. The input is returned literally """ self.widget_type = WIDGET_QUESTION self.message = message self.echo_input = True def password(self, title, message): """ Query for user input. The input is returned literally but not printed back. This is a shortcut to :meth:`dput.interface.AbstractInterface.question` with echo_input defaulting to False """ self.question(title, message, echo_input=False) @abc.abstractmethod def shutdown(self): """ Get rid of everything, close out. """ pass dput-ng-1.7/dput/uploader.py0000644000000000000000000002457012240211077012772 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Uploader implementation. The code in here surrounds the uploaders' implementations, and properly invokes the uploader with correct arguments, etc. """ import os import abc import sys import tempfile import shutil from contextlib import contextmanager import dput.profile from dput.changes import parse_changes_file from dput.core import logger, _write_upload_log from dput.hook import run_pre_hooks, run_post_hooks from dput.util import (run_command, get_obj) from dput.overrides import (make_delayed_upload, force_passive_ftp_upload) from dput.exceptions import (DputConfigurationError, DputError, UploadException) class AbstractUploader(object): """ Abstract base class for all concrete uploader implementations. """ __metaclass__ = abc.ABCMeta def __init__(self, profile): self._config = profile interface = 'cli' if 'interface' in profile: interface = profile['interface'] logger.trace("Using interface %s" % (interface)) interface_obj = get_obj('interfaces', interface) if interface_obj is None: raise DputConfigurationError("No such interface: `%s'" % ( interface )) self.interface = interface_obj() self.interface.initialize() def _pre_hook(self): self._run_hook("pre_upload_command") def _post_hook(self): self._run_hook("post_upload_command") def _run_hook(self, hook): if hook in self._config and self._config[hook] != "": cmd = self._config[hook] (output, stderr, ret) = run_command(cmd) if ret == -1: if not os.path.exists(cmd): logger.warning( "Error: You've set a hook (%s) to run (`%s`), " "but it can't be found (and doesn't appear to exist)." " Please verify the path and correct it." % ( hook, self._config[hook] ) ) return sys.stdout.write(output) # XXX: Fixme if ret != 0: raise DputError( "Command `%s' returned an error: %s [err=%d]" % ( self._config[hook], stderr, ret ) ) def __del__(self): self.interface.shutdown() def upload_write_error(self, e): """ .. warning:: don't call this. please don't call this """ # XXX: Refactor this, please god, refactor this. logger.warning("""Upload permissions error You either don't have the rights to upload a file, or, if this is on ftp-master, you may have tried to overwrite a file already on the server. Continuing anyway in case you want to recover from an incomplete upload. No file was uploaded, however.""") @abc.abstractmethod def initialize(self, **kwargs): """ Setup the things needed to upload a file. Usually this means creating a network connection & authenticating. """ pass @abc.abstractmethod def upload_file(self, filename, upload_filename=None): """ Upload a single file (``filename``) to the server. """ pass @abc.abstractmethod def shutdown(self): """ Disconnect and shutdown. """ pass @contextmanager def uploader(uploader_method, profile, simulate=True): """ Context-managed uploader implementation. Invoke sorta like:: with uploader() as obj: obj.upload_file('filename') This will automatically call that object's :meth:`dput.uploader.AbstractUploader.initialize`, pre-hook, yield the object, call the post hook and invoke it's :meth:`dput.uploader.AbstractUploader.shutdown`. """ cls = get_obj('uploaders', uploader_method) if not cls: logger.error( "Failed to resolve method %s to an uploader class" % ( uploader_method ) ) raise DputConfigurationError( "Failed to resolve method %s to an uploader class" % ( uploader_method ) ) obj = cls(profile) if not simulate or simulate >= 2: obj.initialize() obj._pre_hook() try: yield obj finally: if not simulate: obj._post_hook() if not simulate or simulate >= 2: obj.shutdown() def determine_logfile(changes, conf, args): """ Figure out what logfile to write to. This is mostly an internal implementation. Returns the file to log to, given a changes and profile. """ # dak requires '__<[a-zA-Z0-9+-]+>.changes' # XXX: Correct --force behavior logfile = changes.get_changes_file() # XXX: Check for existing one xtns = [".changes", ".dud"] for xtn in xtns: if logfile.endswith(xtn): logfile = "%s.%s.upload" % (logfile[:-len(xtn)], conf['name']) break else: raise UploadException("File %s does not look like a .changes file" % ( changes.get_filename() )) if ( os.access(logfile, os.R_OK) and os.stat(logfile).st_size > 0 and not args.force ): raise UploadException("""Package %s was already uploaded to %s If you want to upload nonetheless, use --force or remove %s""" % ( changes.get_package_name(), conf['name'], logfile )) logger.debug("Writing log to %s" % (logfile)) return logfile def should_write_logfile(args): return not args.simulate and not args.check_only and not args.no_upload_log def check_modules(profile): if 'hooks' in profile: for hook in profile['hooks']: obj = get_obj('hooks', hook) if obj is None: raise DputConfigurationError( "Error: no such hook '%s'" % ( hook ) ) class DputNamespace(dict): def __getattr__(self, key): return self[key] def __setattr__(self, key, val): self[key] = val def invoke_dput_simple(changes, host, **kwargs): changes = parse_changes_file(changes, os.path.dirname(changes)) # XXX: Abspath??? config = { "host": host, "debug": False, "config": None, "force": False, "simulate": False, "check_only": None, "no_upload_log": None, "full_upload_log": None, "delayed": None, "passive": None, } config.update(kwargs) config = DputNamespace(config) return invoke_dput(changes, config) def invoke_dput(changes, args): """ .. warning:: This method may change names. Please use it via :func:`dput.upload`. also, please don't depend on args, that's likely to change shortly. Given a changes file ``changes``, and arguments to dput ``args``, upload a package to the archive that makes sense. """ profile = dput.profile.load_profile(args.host) check_modules(profile) fqdn = None if "fqdn" in profile: fqdn = profile['fqdn'] else: fqdn = profile['name'] logfile = determine_logfile(changes, profile, args) tmp_logfile = tempfile.NamedTemporaryFile() if should_write_logfile(args): full_upload_log = profile["full_upload_log"] if args.full_upload_log: full_upload_log = args.full_upload_log _write_upload_log(tmp_logfile.name, full_upload_log) if args.delayed: make_delayed_upload(profile, args.delayed) if args.simulate: logger.warning("Not uploading for real - dry run") if args.passive: force_passive_ftp_upload(profile) logger.info("Uploading %s using %s to %s (host: %s; directory: %s)" % ( changes.get_package_name(), profile['method'], profile['name'], fqdn, profile['incoming'] )) if changes.get_changes_file().endswith(".changes"): if 'hooks' in profile: run_pre_hooks(changes, profile) else: logger.trace(profile) logger.warning("No hooks defined in the profile. " "Not checking upload.") # check only is a special case of -s if args.check_only: args.simulate = 1 with uploader(profile['method'], profile, simulate=args.simulate) as obj: if args.check_only: logger.info("Package %s passes all checks" % ( changes.get_package_name() )) return if args.no_upload_log: logger.info("Not writing upload log upon request") files = changes.get_files() + [changes.get_changes_file()] for path in files: logger.info("Uploading %s%s" % ( os.path.basename(path), " (simulation)" if args.simulate else "" )) if not args.simulate: obj.upload_file(path) if args.simulate: return if changes.get_changes_file().endswith(".changes"): if 'hooks' in profile: run_post_hooks(changes, profile) else: logger.trace(profile) logger.warning("No hooks defined in the profile. " "Not post-processing upload.") if should_write_logfile(args): tmp_logfile.flush() shutil.copy(tmp_logfile.name, logfile) #print(tmp_logfile.name) tmp_logfile.close() dput-ng-1.7/dput/overrides.py0000644000000000000000000000313112114414240013144 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # XXX: document. import os from dput.core import logger def make_delayed_upload(conf, delayed_days): """ DELAYED uploads to ftp-master eventually means to use another incoming directory instead of the default. This is easy enough to be implemented Mangles the supplied configuration object """ incoming_directory = os.path.join( conf['incoming'], "DELAYED", "%d-day" % (delayed_days) ) logger.debug("overriding upload directory to %s" % (incoming_directory)) conf['incoming'] = incoming_directory def force_passive_ftp_upload(conf): """ Force FTP to use passive mode. Mangles the supplied configuration object """ logger.debug("overriding configuration to force FTP passive mode") conf['passive_ftp'] = True dput-ng-1.7/dput/logger.py0000644000000000000000000000207412114414240012426 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # XXX: Document me. import logging TRACE = 5 logging.addLevelName(TRACE, 'TRACE') class DputLogger(logging.getLoggerClass()): def trace(self, msg, *args, **kwargs): if self.isEnabledFor(TRACE): self._log(TRACE, msg, args, **kwargs) dput-ng-1.7/dput/configs/0000755000000000000000000000000012241262032012223 5ustar dput-ng-1.7/dput/configs/dputcf.py0000644000000000000000000001035312215340272014070 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Old dput config file implementation """ import os import sys if sys.version_info[0] >= 3: import configparser else: import ConfigParser as configparser import dput.core from dput.config import AbstractConfig from dput.core import logger from dput.exceptions import DputConfigurationError class DputCfConfig(AbstractConfig): """ dput-old config file implementation. Subclass of a :class:`dput.config.AbstractConfig`. """ def preload(self, configs): """ See :meth:`dput.config.AbstractConfig.preload` """ parser = configparser.ConfigParser() if configs is None: configs = dput.core.DPUT_CONFIG_LOCATIONS for config in configs: if not os.access(config, os.R_OK): logger.debug("Skipping file %s: Not accessible" % ( config )) continue try: logger.trace("Parsing %s" % (config)) parser.readfp(open(config, 'r')) except IOError as e: logger.warning("Skipping file %s: %s" % ( config, e )) continue except configparser.ParsingError as e: raise DputConfigurationError("Error parsing file %s: %s" % ( config, e )) self.parser = parser self.configs = configs self.defaults = self._translate_strs(self.get_config("DEFAULT")) self.parser.remove_section("DEFAULT") def set_replacements(self, replacements): """ See :meth:`dput.config.AbstractConfig.set_replacements` """ for replacement in replacements: if self.parser.has_section(replacement): self.parser.set(replacement, replacement, replacements[replacement]) def get_config_blocks(self): """ See :meth:`dput.config.AbstractConfig.get_config_blocks` """ return self.parser.sections() def get_defaults(self): """ See :meth:`dput.config.AbstractConfig.get_defaults` """ return self.defaults def _translate_strs(self, ret): trans = { "1": True, "0": False } ret = self._translate_dict(ret, trans) return ret def _translate_bools(self, ret): trans = { True: "1", False: "0" } return self._translate_dict(ret, trans) def _translate_dict(self, ret, trans): if isinstance(ret, dict): ret = ret.copy() elif isinstance(ret, list): ret = ret[:] for key in ret: val = ret[key] if isinstance(val, dict) or isinstance(val, list): ret[key] = self._translate_dict(val, trans) continue if val in trans: val = trans[val] ret[key] = val return ret def get_config(self, name, ignore_errors=False): """ See :meth:`dput.config.AbstractConfig.get_config` """ ret = {} try: items = self.parser.items(name) except configparser.NoSectionError: return {} for key, val in items: ret[key] = val ret['name'] = name ret = self._translate_strs(ret) return ret dput-ng-1.7/dput/configs/__init__.py0000644000000000000000000000146512114414240014341 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. dput-ng-1.7/dput/configs/dputng.py0000644000000000000000000000652012215340272014105 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ dput-ng native configuration file implementation. """ import sys from dput.util import load_config, get_configs from dput.core import logger from dput.config import AbstractConfig from dput.exceptions import DputConfigurationError def get_sections(): """ Get all profiles we know about. """ return get_configs('profiles') if sys.version_info[0] >= 3: _basestr_type = str else: _basestr_type = basestring class DputProfileConfig(AbstractConfig): """ dput-ng native config file implementation. Subclass of a :class:`dput.config.AbstractConfig`. """ def preload(self, configs): """ See :meth:`dput.config.AbstractConfig.preload` """ self.configs = configs self.replacements = {} self.cache = {} self.defaults = {} self.defaults = self.get_config("DEFAULT") def set_replacements(self, replacements): """ See :meth:`dput.config.AbstractConfig.set_replacements` """ self.replacements = replacements def get_config_blocks(self): """ See :meth:`dput.config.AbstractConfig.get_config_blocks` """ return get_sections() def get_defaults(self): """ See :meth:`dput.config.AbstractConfig.get_defaults` """ return self.defaults.copy() def get_config(self, name, ignore_errors=False): """ See :meth:`dput.config.AbstractConfig.get_config` """ kwargs = { "default": {} } configs = self.configs if configs is not None: kwargs['configs'] = configs kwargs['config_cleanup'] = False profile = load_config( 'profiles', name, **kwargs ) logger.trace("name: %s - %s / %s" % (name, profile, kwargs)) repls = self.replacements for thing in profile: val = profile[thing] if not isinstance(val, _basestr_type): continue for repl in repls: if repl in val: val = val.replace("%%(%s)s" % (repl), repls[repl]) profile[thing] = val ret = {} ret.update(profile) ret['name'] = name for key in ret: val = ret[key] if isinstance(val, _basestr_type): if "%(" in val and ")s" in val and not ignore_errors: raise DputConfigurationError( "Half-converted block: %s --> %s" % (key, val)) return ret dput-ng-1.7/dput/uploaders/0000755000000000000000000000000012241261643012600 5ustar dput-ng-1.7/dput/uploaders/ftp.py0000644000000000000000000000715412114414240013742 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ FTP Uploader implementation. """ import ftplib import os.path from dput.core import logger from dput.uploader import AbstractUploader from dput.exceptions import UploadException class FtpUploadException(UploadException): """ Thrown in the event of a problem connecting, uploading to or terminating the connection with the remote server. This is a subclass of :class:`dput.exceptions.UploadException`. """ pass class FtpUploader(AbstractUploader): """ Provides an interface to upload files through FTP. Supports anonymous uploads only for the time being. This is a subclass of :class:`dput.uploader.AbstractUploader` """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ logger.debug("Logging into %s as %s" % ( self._config["fqdn"], self._config["login"] )) conf = self._config['ftp'] if 'ftp' in self._config else {} timeout = conf['timeout'] if 'timeout' in conf else 10 try: self._ftp = ftplib.FTP( self._config["fqdn"], self._config["login"], None, timeout=timeout ) except Exception as e: raise FtpUploadException( "Could not establish FTP connection to %s: %s" % ( self._config['fqdn'], e ) ) if self._config["passive_ftp"] or kwargs['passive_mode']: logger.debug("Enable PASV mode") self._ftp.set_pasv(True) if self._config["incoming"]: logger.debug("Change directory to %s" % ( self._config["incoming"] )) try: self._ftp.cwd(self._config["incoming"]) except ftplib.error_perm as e: raise FtpUploadException( "Could not change directory to %s: %s" % ( self._config["incoming"], e ) ) def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ if not upload_filename: upload_filename = os.path.basename(filename) try: basename = "STOR %s" % (upload_filename) self._ftp.storbinary(basename, open(filename, 'rb')) except ftplib.error_perm as e: self.upload_write_error(e) except Exception as e: raise FtpUploadException("Could not upload file %s: %s" % ( upload_filename, e )) def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ self._ftp.quit() dput-ng-1.7/dput/uploaders/http.py0000644000000000000000000000744012114414240014126 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ HTTP Uploader implementation """ from dput.exceptions import UploadException from dput.uploader import AbstractUploader from dput.core import logger import urllib2 import mmap import mimetypes import os.path import urlparse class HttpUploadException(UploadException): """ Thrown in the event of a problem connecting, uploading to or terminating the connection with the remote server. This is a subclass of :class:`dput.exceptions.UploadException`. """ pass class HTTPUploader(AbstractUploader): """ Provides an interface to upload files through HTTP. Supports only anonymous uploads for the time being. This is a subclass of :class:`dput.uploader.AbstractUploader` """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ mimetypes.init() # code below is fugly. Dear god, please write a mutable # urlparse library. Pretty please. Hopefully the mangling below is # reasonably sane if not self._config['fqdn'].lower().startswith("http"): self._config['fqdn'] = "http://" + self._config['fqdn'] self._baseurl = urlparse.urlparse(self._config['fqdn']) _incoming = self._config['incoming'] if not _incoming.startswith("/"): _incoming = "/" + _incoming if not _incoming.endswith("/"): _incoming = _incoming + "/" _path = self._baseurl.path if not _path: _path = _incoming else: _path = _path + _incoming _query = self._baseurl.query self._username = self._baseurl.username self._password = self._baseurl.password # XXX: Timeout, please. self._baseurl = urlparse.urlunparse((self._baseurl.scheme, self._baseurl.netloc, _path, _query, self._username, self._password )) def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ upload_filename = self._baseurl + os.path.basename(filename) logger.debug("Upload to %s" % (upload_filename)) (mime_type, _) = mimetypes.guess_type(filename) fh = open(filename, 'rb') mmaped_fh = mmap.mmap(fh.fileno(), 0, access=mmap.ACCESS_READ) req = urllib2.Request(url=upload_filename, data=mmaped_fh) req.add_header("Content-Type", mime_type) req.get_method = lambda: 'PUT' try: urllib2.urlopen(req) except urllib2.HTTPError as e: if e.code == 403: self.upload_write_error(e) else: raise HttpUploadException(e) mmaped_fh.close() fh.close() def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ pass dput-ng-1.7/dput/uploaders/scp.py0000644000000000000000000000561212114414240013733 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ SCP Uploader implementation. .. warning:: This is deprecated. Please use SFTP """ import os.path from dput.core import logger from dput.uploader import AbstractUploader from dput.exceptions import UploadException from dput.uploaders.sftp import find_username from dput.util import run_command class ScpUploadException(UploadException): """ Thrown in the event of a problem connecting, uploading to or terminating the connection with the remote server. This is a subclass of :class:`dput.exceptions.UploadException`. """ pass class ScpUploader(AbstractUploader): """ Provides an interface to upload files through SCP. This is a subclass of :class:`dput.uploader.AbstractUploader` """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ login = find_username(self._config) self._scp_base = ["scp", "-p", "-C"] # XXX: Timeout? if 'port' in self._config: self._scp_base += ("-P", "%s" % self._config['port']) self._scp_host = "%s@%s" % (login, self._config['fqdn']) logger.debug("Using scp to upload to %s" % (self._scp_host)) logger.warning("SCP is deprecated. Please consider upgrading to SFTP.") def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ if not upload_filename: upload_filename = os.path.basename(filename) incoming = self._config['incoming'] targetfile = "%s:%s" % (self._scp_host, os.path.join(incoming, upload_filename)) scp = self._scp_base + [filename, targetfile] #logger.debug("run: %s" % (scp)) (_, e, x) = run_command(scp) if x != 0: raise ScpUploadException("Failed to upload %s to %s: %s" % ( upload_filename, targetfile, e) ) def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ pass dput-ng-1.7/dput/uploaders/__init__.py0000644000000000000000000000146512114414240014707 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. dput-ng-1.7/dput/uploaders/secure_sftp.py0000644000000000000000000005617712160717773015526 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2013 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your Option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ SFTP Uploader implementation still EXPERIMENTAL, and only working with python >= 3.2 """ import os.path import subprocess import select from dput.core import logger from dput.uploader import AbstractUploader from dput.exceptions import UploadException class EnumInternException(Exception): def __init__(self, v): super().__init__(v) self.value = v class Enum: def __init__(self, name, value): if isinstance(name, str): self.name = name else: self.name = name[0] self.__name__ = self.name self.value = value type(self).byvalue[value] = self if isinstance(name, str): type(self).byname[name] = self setattr(type(self), name, self) else: for n in name: type(self).byname[n] = self setattr(type(self), n, self) def __int__(self): return self.value def __str__(self): return self.name @classmethod def intern(cls, l): try: if isinstance(l, cls): return l elif isinstance(l, int): return cls.byvalue[l] elif isinstance(l, str): return cls.byname[l] else: raise EnumInternException(repr(l)) except KeyError: raise EnumInternException(repr(l)) @classmethod def create(cls, name, values): class enum(Enum): __name__ = name byvalue = dict() byname = dict() for (k, v) in values.items(): e = enum(k, v) return enum class Bitmask(set): byname = dict() byvalue = dict() def __init__(self, l): if isinstance(l, int): super().__init__( [i for (k, i) in self.Values.byvalue.items() if (l & i.mask) == k]) if l != int(self): raise Exception( "Unrepresentable number %d (got parsed as %s = %d)" % (l, str(self), int(self)) ) elif isinstance(l, str): try: super().__init__([self.Values.intern(i) for i in l.split("|")]) # test for inconsistencies: type(self)(int(self)) except EnumInternException as e: raise Exception("Invalid value '%s' in value '%s' for %s" % (e.value, str(l), type(self).__name__)) else: try: super().__init__([self.Values.intern(i) for i in l]) # test for inconsistencies: type(self)(int(self)) except EnumInternException as e: raise Exception("Invalid value '%s' in value '%s' for %s" % (e.value, str(l), type(self).__name__)) def __int__(self): v = 0 for i in self: v = v | int(i) return v def __str__(self): return "|".join([str(i) for i in self]) @classmethod def create(cls, name, values): class bitmask(Bitmask): __name__ = "Bitmask of " + name class Values(Enum): __name__ = name byvalue = dict() byname = dict() bitmask.__name__ = "Bitmask of " + name for (k, v) in values.items(): if isinstance(v, int): e = bitmask.Values(k, v) e.mask = v else: e = bitmask.Values(k, v[0]) e.mask = v[1] return bitmask class SftpUploadException(UploadException): """ Thrown in the event of a problem connecting, uploading to or terminating the connection with the remote server. This is a subclass of :class:`dput.exceptions.UploadException`. """ pass # Unparseable stuff from server: class SftpStrangeException(SftpUploadException): pass class SftpUnexpectedAnswerException(SftpStrangeException): def __init__(self, answer, request): super().__init__("Unexpected answer '%s' to request '%s'" % (str(answer), str(request))) class SftpTooManyRequestsException(SftpUploadException): def __init__(self): super().__init__("Too many concurrent requests (out of request ids)") # a programming or programmer mistake: class SftpInternalException(SftpUploadException): pass def ssh_data(b): return len(b).to_bytes(4, byteorder='big') + b def ssh_string(s): b = str(s).encode(encoding='utf-8') return len(b).to_bytes(4, byteorder='big') + b def ssh_u8(i): return int(i).to_bytes(1, byteorder='big') def ssh_u32(i): return int(i).to_bytes(4, byteorder='big') def ssh_u64(i): return int(i).to_bytes(8, byteorder='big') def ssh_attrs(**opts): return int(0).to_bytes(4, byteorder='big') def ssh_getu32(m): v = int.from_bytes(m[:4], byteorder='big') return v, m[4:] def ssh_getstring(m): l = int.from_bytes(m[:4], byteorder='big') return (m[4:4 + l].decode(encoding='utf-8'), m[4 + l:]) def ssh_getdata(m): l = int.from_bytes(m[:4], byteorder='big') return (m[4:4 + l], m[4 + l:]) class Sftp: class Request: def __init__(self): pass def __int__(self): return self.requestid @classmethod def bin(cls, req, *payload): if isinstance(req, int): r = req else: r = req.requestid s = 5 for b in payload: s = s + len(b) binary = ssh_u32(s) + ssh_u8(cls.typeid) + ssh_u32(r) for b in payload: binary = binary + b return binary def done(self): if self.requestid != None: del self.conn.requests[self.requestid] self.requestid = None class INIT(Request): typeid = 1 @classmethod def bin(cls, version): # INIT has no request id but instead sends a protocol version return super().bin(int(version)) VERSION = 2 class OPEN(Request): typeid = 3 Flags = Bitmask.create("SSH_FXF", { "READ": 0x00000001, "WRITE": 0x00000002, "APPEND": 0x00000004, "CREAT": 0x00000008, "TRUNC": 0x00000010, "EXCL": 0x00000020, }) def __init__(self, name, flags, **attributes): super().__init__() self.name = name self.flags = self.Flags(flags) self.attrs = attributes @classmethod def bin(cls, req, name, flags, attrs): return super().bin(req, ssh_string(name), ssh_u32(flags), ssh_attrs(**attrs)) def send(self, conn): conn.send(self.bin(self, self.name, self.flags, self.attrs)) def __str__(self): return "OPEN '%s' %s=%d" % (self.name, self.flags, self.flags) class CLOSE(Request): typeid = 4 def __init__(self, handle): super().__init__() self.handle = handle @classmethod def bin(cls, req, handle): return super().bin(req, ssh_data(handle)) def send(self, conn): conn.send(self.bin(self, self.handle)) def __str__(self): return "close %s" % (self.handle) READ = 5 class WRITE(Request): typeid = 6 def __init__(self, handle, start, data): super().__init__() self.handle = handle self.start = start self.data = data @classmethod def bin(cls, req, handle, start, data): return super().bin(req, ssh_data(handle), ssh_u64(start), ssh_data(bytes(data))) def send(self, conn): conn.send(self.bin(self, self.handle, self.start, self.data)) def __str__(self): return "write %s,%d,%s" % (repr(self.handle), self.start, repr(self.data)) LSTAT = 7 FSTAT = 8 SETSTAT = 9 FSETSTAT = 10 OPENDIR = 11 READDIR = 12 class REMOVE(Request): typeid = 13 def __init__(self, name): super().__init__() self.name = name @classmethod def bin(cls, req, name): return super().bin(req, ssh_string(name)) def send(self, conn): conn.send(self.bin(self, self.name)) def __str__(self): return "rm '%s'" % self.name class MKDIR(Request): typeid = 14 def __init__(self, directory, **attrs): super().__init__() self.dir = directory self.attrs = attrs @classmethod def bin(cls, req, directory, attrs): return super().bin(req, ssh_string(directory), ssh_attrs(**attrs)) def send(self, conn): conn.send(self.bin(self, self.dir, self.attrs)) def __str__(self): return "mkdir '%s'" % self.dir class RMDIR(Request): typeid = 15 def __init__(self, directory): super().__init__() self.dir = directory @classmethod def bin(cls, req, directory): return super().bin(req, ssh_string(directory)) def send(self, conn): conn.send(self.bin(self, self.dir)) def __str__(self): return "rmdir '%s'" % self.dir REALPATH = 16 STAT = 17 class RENAME(Request): typeid = 18 Flags = Bitmask.create("SSH_FXF_RENAME", { 'OVERWRITE': 0x00000001, 'ATOMIC': 0x00000002, 'NATIVE': 0x00000004}) def __init__(self, src, dst, flags): super().__init__() self.src = src self.dst = dst if isinstance(flags, self.Flags): self.flags = flags else: self.flags = self.Flags(flags) @classmethod def bin(cls, req, src, dst, flags): return super().bin(req, ssh_string(src), ssh_string(dst), ssh_u32(flags)) def send(self, conn): conn.send(self.bin(self, self.src, self.dst, self.flags)) def __str__(self): return "rename '%s' into '%s' (%s)" % ( self.src, self.dst, str(self.flags)) READLINK = 19 SYMLINK = 20 EXTENDED = 200 EXTENDED_REPLY = 201 class Answer: def __int__(self): return self.typeid class STATUS(Answer): Status = Enum.create("SSH_FX", { "OK": 0, "EOF": 1, "NO_SUCH_FILE": 2, "PERMISSION_DENIED": 3, "FAILURE": 4, "BAD_MESSAGE": 5, "NO_CONNECTION": 6, "CONNECTION_LOST": 7, "OP_UNSUPPORTED": 8, "INVALID_HANDLE": 9, "NO_SUCH_PATH": 10, "FILE_ALREADY_EXISTS": 11, "WRITE_PROTECT": 12, "NO_MEDIA": 13}) id = 101 def __init__(self, m): s, m = ssh_getu32(m) self.status = self.Status.intern(s) self.message, m = ssh_getstring(m) self.lang, m = ssh_getstring(m) def __str__(self): return "STATUS %s: %s[%s]" % ( str(self.status), self.message, self.lang) class HANDLE(Answer): id = 102 def __init__(self, m): self.handle, m = ssh_getdata(m) def __str__(self): return "HANDLE %s" % repr(self.handle) class DATA(Answer): id = 103 def __init__(self, m): self.data, m = ssh_getdata(m) def __str__(self): return "DATA %s" % repr(self.data) class NAME(Answer): id = 104 def __init__(self, m): # TODO pass def __str__(self): return "NAME" class ATTRS(Answer): id = 105 def __init__(self, m): # TODO pass def __str__(self): return "ATTRS" class Task: def start(self, connection): self.connection = connection def enqueuejobs(self, jobs): self.connection.enqueue(jobs, self) class TaskFromGenerator(Task): def __init__(self, gen): super().__init__() self.gen = gen def start(self, connection): super().start(connection) self.enqueuejobs(next(self.gen)) def parentinfo(self, command): self.enqueuejobs(self.gen.send(command)) def sftpanswer(self, answer): self.enqueuejobs(self.gen.send(answer)) def __str__(self): return "Task(by %s)" % self.gen def next_request_id(self): i = self.requestid_try_next while i in self.requests: i = (i + 1) % 0x100000000 if i == self.requestid_try_next: raise SftpTooManyRequestsException() self.requestid_try_next = (i + 1) % 0x100000000 return i def __init__(self, **options): self.requests = dict() self.queue = list() self.requestid_try_next = 17 commandline = ["ssh"] if "ssh_options" in options: commandline.extend(options["ssh_options"]) # those defaults are after the user-supplied ones so they can be overriden. # (earlier ones win with ssh). commandline.extend(["-oProtocol 2", # "-oLogLevel DEBUG", "-oForwardX11 no", "-oForwardAgent no", "-oPermitLocalCommand no", "-oClearAllForwardings yes"]) if "username" in options and options["username"]: commandline.extend(["-l", options["username"]]) commandline.extend(["-s", "--", options["servername"], "sftp"]) print(commandline) self.connection = subprocess.Popen(commandline, close_fds=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) self.poll = select.poll() self.poll.register(self.connection.stdout, select.POLLIN) self.inbuffer = bytes() self.send(self.INIT.bin(3)) t, b = self.getpacket() if t != self.VERSION: raise SftpUnexpectedAnswerException(b, "INIT") # TODO: parse answer data (including available extensions) def close(self): self.connection.send_signal(15) def getmoreinput(self, minlen): while len(self.inbuffer) < minlen: o = self.connection.stdout.read(minlen - len(self.inbuffer)) if o == None: continue if len(o) == 0: raise SftpStrangeException("unexpected EOF") self.inbuffer = self.inbuffer + o def getpacket(self): self.getmoreinput(5) s = int.from_bytes(self.inbuffer[:4], byteorder='big') if s < 1: raise SftpStrangeException("Strange size field in Paket from server!") s = s - 1 t = self.inbuffer[4] self.inbuffer = self.inbuffer[5:] self.getmoreinput(s) d = self.inbuffer[:s] self.inbuffer = self.inbuffer[s:] #print("got: ", t, d) return (t, d) def send(self, b): if not isinstance(b, bytes): raise SftpInternalException("send not given byte sequence") #print(b) self.connection.stdin.write(b) def enqueue(self, joblist, gen): if len(joblist) == 0: return if len(self.queue) == 0: self.poll.register(self.connection.stdin, select.POLLOUT) for job in joblist: if isinstance(job, self.Request): job.task = gen else: raise SftpUploadException("Unexpected data from task: %s" % repr(gen)) self.queue.extend(joblist) def start(self, task): task.start(self) def dispatchanswer(self, answer): task = answer.forr.task try: task.sftpanswer(answer) except StopIteration: orphanreqs = [r for r in self.requests.values() if r.task == task] for r in orphanreqs: r.done() def readdata(self): t, m = self.getpacket() for answer in self.Answer.__subclasses__(): if t == answer.id: request_id, m = ssh_getu32(m) a = answer(m) if not request_id in self.requests: raise SftpUnexpectedAnswerException(a, "unknown-id-%d" % request_id) else: a.forr = self.requests[request_id] self.dispatchanswer(a) break else: raise SftpUnexpectedAnswerException("Unknown answer type %d" % t, "") def dispatch(self): while self.requests or self.queue: for (_, event) in self.poll.poll(): if event == select.POLLIN: self.readdata() elif event == select.POLLHUP: raise SftpStrangeException( "Server disconnected unexpectedly") elif event == select.POLLOUT: request = self.queue.pop(0) request.requestid = self.next_request_id() request.conn = self self.requests[request.requestid] = request request.send(self) if len(self.queue) == 0: self.poll.unregister(self.connection.stdin) else: raise SftpUploadException( "Unexpected event %d from poll" % event) def put(self, filename, localfilename): self.start(Sftp.TaskFromGenerator(writefile(filename, localfilename))) self.dispatch() class filepart: def __init__(self, fh, start, length): self.fh = fh self.start = start self.len = length def __bytes__(self): self.fh.seek(self.start, 0) b = self.fh.read(self.len) while len(b) < self.len: b = b + self.fh.read(self.len - len(b)) return b def writefile(filename, localfile): localf = open(localfile, 'rb') size = os.fstat(localf.fileno()).st_size a = yield [Sftp.OPEN(filename, "CREAT|WRITE|TRUNC")] a.forr.done() if isinstance(a, Sftp.STATUS): if a.status == a.Status.NO_SUCH_FILE: raise SftpUploadException("Failed to create %s: No such file. (Perhaps the directory is missing?)" % filename) else: raise SftpUploadException("Failed to create %s: %s" % (filename, a)) if not isinstance(a, Sftp.HANDLE): raise SftpUnexpectedAnswerException(a, a.forr) h = a.handle a.forr.done() ranges = list(range(0, size, 32600)) if ranges: requests = [Sftp.WRITE(h, r, filepart(localf, r, 32600)) for r in ranges[:-1]] requests.append(Sftp.WRITE(h, ranges[-1], filepart(localf, ranges[-1], size - ranges[-1]))) a = yield requests while requests: a.forr.done() if not isinstance(a, Sftp.STATUS): raise SftpUnexpectedAnswerException(a, a.forr) elif a.status != a.Status.OK: raise SftpUploadException("Error writing to %s: %s: %s" % ( filename, a.forr, a)) requests.remove(a.forr) a = yield [] a = yield [Sftp.CLOSE(h)] a.forr.done() if not isinstance(a, Sftp.STATUS): raise SftpUnexpectedAnswerException(a, a.forr) elif a.status != a.Status.OK: raise SftpUploadException("Error writing to %s: %s: %s" % ( filename, a.forr, a)) class SFTPUploader(AbstractUploader): """ Provides an interface to upload files through SFTP. This is a subclass of :class:`dput.uploader.AbstractUploader` """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ fqdn = self._config['fqdn'] incoming = self._config['incoming'] ssh_options = [] if "ssh_options" in self._config: ssh_options.extend(self._config['ssh_options']) if 'port' in self._config: ssh_options.append("-oPort=%d" % self._config['port']) username = None if 'login' in self._config and self._config['login'] != "*": username = self._config['login'] if incoming.startswith('~/'): logger.warning("SFTP does not support ~/path, continuing with" "relative directory name instead.") incoming = incoming[2:] if username: logger.info("Logging into host %s as %s" % (fqdn, username)) else: logger.info("Logging into host %s" % fqdn) self._sftp = Sftp(servername=fqdn, username=username, ssh_options=ssh_options) self.incoming = incoming def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ if not upload_filename: upload_filename = os.path.basename(filename) upload_filename = os.path.join(self.incoming, upload_filename) logger.debug("Writing to: %s" % (upload_filename)) self._sftp.put(upload_filename, filename) def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ self._sftp.close() dput-ng-1.7/dput/uploaders/sftp.py0000644000000000000000000002106312160717773014142 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your Option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ SFTP Uploader implementation """ import paramiko import socket import os import pwd import os.path from binascii import hexlify from dput.core import logger from dput.uploader import AbstractUploader from dput.exceptions import UploadException class SftpUploadException(UploadException): """ Thrown in the event of a problem connecting, uploading to or terminating the connection with the remote server. This is a subclass of :class:`dput.exceptions.UploadException`. """ pass def find_username(conf): """ Given a profile (``conf``), return the preferred username to login with. It falls back to getting the logged in user's name. """ user = None user = pwd.getpwuid(os.getuid()).pw_name if 'login' in conf: new_user = conf['login'] if new_user != "*": user = new_user if not user: raise SftpUploadException( "No user to upload could be retrieved. " "Please set 'login' explicitly in your profile" ) return user class AskToAccept(paramiko.AutoAddPolicy): """ Paramiko policy to automatically add the hostname, but only after asking. """ def __init__(self, uploader): super(AskToAccept, self).__init__() self.uploader = uploader def missing_host_key(self, client, hostname, key): accept = self.uploader.interface.boolean( title='please login', message='To accept %s hostkey %s for %s type "yes":' % ( key.get_name(), hexlify(key.get_fingerprint()), hostname ) ) if accept: super(AskToAccept, self).missing_host_key(client, hostname, key) else: raise paramiko.SSHException('Unknown server %s' % hostname) class SFTPUploader(AbstractUploader): """ Provides an interface to upload files through SFTP. This is a subclass of :class:`dput.uploader.AbstractUploader` """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ fqdn = self._config['fqdn'] incoming = self._config['incoming'] self.sftp_config = {} if "sftp" in self._config: self.sftp_config = self._config['sftp'] self.putargs = {'confirm': False} if "confirm_upload" in self.sftp_config: self.putargs['confirm'] = self.sftp_config['confirm_upload'] if incoming.startswith('~/'): logger.warning("SFTP does not support ~/path, continuing with" "relative directory name instead.") incoming = incoming[2:] # elif incoming.startswith('~') and not self.host_is_launchpad: # raise SftpUploadException("SFTP doesn't support ~path. " # "if you need $HOME paths, use SCP.") # XXX: What to do here?? - PRT ssh_kwargs = { "port": 22, "compress": True } # XXX: Timeout override if 'port' in self._config: ssh_kwargs['port'] = self._config['port'] if 'scp_compress' in self._config: ssh_kwargs['compress'] = self._config['scp_compress'] config = paramiko.SSHConfig() if os.path.exists('/etc/ssh/ssh_config'): config.parse(open('/etc/ssh/ssh_config')) if os.path.exists(os.path.expanduser('~/.ssh/config')): config.parse(open(os.path.expanduser('~/.ssh/config'))) o = config.lookup(fqdn) user = find_username(self._config) if "user" in o: user = o['user'] ssh_kwargs['username'] = user if 'identityfile' in o: pkey = os.path.expanduser(o['identityfile']) ssh_kwargs['key_filename'] = pkey logger.info("Logging into host %s as %s" % (fqdn, user)) self._sshclient = paramiko.SSHClient() if 'globalknownhostsfile' in o: for gkhf in o['globalknownhostsfile'].split(): if os.path.isfile(gkhf): self._sshclient.load_system_host_keys(gkhf) else: files = [ "/etc/ssh/ssh_known_hosts", "/etc/ssh/ssh_known_hosts2" ] for fpath in files: if os.path.isfile(fpath): self._sshclient.load_system_host_keys(fpath) if 'userknownhostsfile' in o: for u in o['userknownhostsfile'].split(): # actually, ssh supports a bit more than ~/, # but that would be a task for paramiko... ukhf = os.path.expanduser(u) if os.path.isfile(ukhf): self._sshclient.load_host_keys(ukhf) else: for u in ['~/.ssh/known_hosts2', '~/.ssh/known_hosts']: ukhf = os.path.expanduser(u) if os.path.isfile(ukhf): # Ideally, that should be load_host_keys, # so that the known_hosts file can be written # again. But paramiko can destroy the contents # or parts of it, so no writing by using # load_system_host_keys here, too: self._sshclient.load_system_host_keys(ukhf) self._sshclient.set_missing_host_key_policy(AskToAccept(self)) self._auth(fqdn, ssh_kwargs) try: self._sftp = self._sshclient.open_sftp() except paramiko.SSHException as e: raise SftpUploadException( "Error opening SFTP channel to %s (perhaps sftp is " "disabled there?): %s" % ( fqdn, repr(e) ) ) # logger.debug("Changing directory to %s" % (incoming)) # self._sftp.chdir(incoming) self.incoming = incoming def _auth(self, fqdn, ssh_kwargs, _first=0): if _first == 3: raise SftpUploadException("Failed to authenticate") try: self._sshclient.connect(fqdn, **ssh_kwargs) logger.debug("Logged in!") except socket.error as e: raise SftpUploadException("SFTP error uploading to %s: %s" % ( fqdn, repr(e) )) except paramiko.AuthenticationException: logger.warning("Failed to auth. Prompting for a login pair.") # XXX: Ask for pw only user = self.interface.question('please login', 'Username') # 4 first error pw = self.interface.password(None, "Password") if user is not None: ssh_kwargs['username'] = user ssh_kwargs['password'] = pw self._auth(fqdn, ssh_kwargs, _first=_first + 1) except paramiko.SSHException as e: raise SftpUploadException("SFTP error uploading to %s: %s" % ( fqdn, repr(e) )) def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ if not upload_filename: upload_filename = os.path.basename(filename) upload_filename = os.path.join(self.incoming, upload_filename) logger.debug("Writing to: %s" % (upload_filename)) try: self._sftp.put(filename, upload_filename, **self.putargs) except IOError as e: if e.errno == os.errno.EACCES: self.upload_write_error(e) else: raise SftpUploadException("Could not upload file %s: %s" % ( filename, e )) def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ self._sshclient.close() self._sftp.close() dput-ng-1.7/dput/uploaders/local.py0000644000000000000000000000377512114414240014250 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Local Uploader implementation """ from dput.uploader import AbstractUploader import dput.util import os.path class LocalUploader(AbstractUploader): """ Provides an interface to "upload" files to the local filesystem. This is helpful when you're dputting to the same system you're currently on, and do not wish to use `scp` or `sftp` as the transport (which is totally understandable). """ def initialize(self, **kwargs): """ See :meth:`dput.uploader.AbstractUploader.initialize` """ pass def upload_file(self, filename, upload_filename=None): """ See :meth:`dput.uploader.AbstractUploader.upload_file` """ #TODO: Fix me later. install does not support renaming assert(upload_filename is None) whereto = self._config['incoming'] whereto = os.path.expanduser(whereto) if "HOME" in os.environ: whereto = os.path.join(os.environ["HOME"], whereto) dput.util.run_command([ "install", filename, whereto ]) def shutdown(self): """ See :meth:`dput.uploader.AbstractUploader.shutdown` """ pass dput-ng-1.7/dput/config.py0000644000000000000000000000442412215340272012422 0ustar # -*- coding: utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # Copyright (c) 2012 dput authors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """ Implementation regarding configuration files & their internal representation to the dput profile code. """ import abc class AbstractConfig(object): """ Abstract Configuration Object. All concrete configuration implementations must subclass this object. Basically, all subclasses are bootstrapped in the same-ish way: * preload * get_defaults * set defaults """ __metaclass__ = abc.ABCMeta def __init__(self, configs): self.preload(configs) self.path = ' '.join(configs) @abc.abstractmethod def set_replacements(self, replacements): """ """ pass # XXX: DOCUMENT ME. @abc.abstractmethod def get_defaults(self): """ Get the defaults that concrete configs get overlaid on top of. In theory, this is set during the bootstrapping process. """ pass @abc.abstractmethod def get_config(self, name, ignore_errors=False): """ Get a configuration block. What this means is generally up to the implementation. However, please keep things sane and only return sensual upload target blocks. """ pass @abc.abstractmethod def get_config_blocks(self): """ Get a list of all configuration blocks. Strings in a list. """ pass @abc.abstractmethod def preload(self, replacements): """ Load all configuration blocks. """ pass dput-ng-1.7/TODO0000644000000000000000000000561012215340272010315 0ustar TODO list for dput-ng ===================== Important issues ================ Allow sane --force behavior for DM permissions if the key isn't in the ring for some reason. end important issues ==================== prime hacks neededing to be done $ grep -E "(TODO|XXX)" . -r 09:18 < daemonkeeper> If you're bored crossing the continent I think having a critic look at checkers is more worthwhile. at least my todos with checkers are a major construction site. We've a stub file in there which either needs refactoring or a removal, a broken checker, and a checker which only exists as a quotation by me. [22:18] Oh, I know another feature I want. for -2 upload, I want # to check whether the orig checksum matches the -1 upload. dirt ---- borked, needs user-facing cleanup (args, switches, etc, very confusing tool) implement me properly dput-config ftp-master --global --enable-checker 'gpg' dput-config localhost --local --enable-processor 'tweet' dput-config ftp-master --get fqdn dput-config ftp-master --blame dput-bugreport -------------- implement me. dump all stuff we need to properly grok issues. dump targets, last run information, full blame, etc. Hook it up with debbugs and ubuntu's voodoo. bin/dput -------- - implement --force switch (bypass all checker, ignore .upload file?) config loader ------------- * make sure schema, scripts and other non-configs won't be attempted to be loaded :) * Make it possible to pass dcut commands our profile. That's a bit of a tricky situation, see the comment in dput/command.py for details. Once that's done refactor the command classes which currently load the profile in their validate() methods. * **MARK** Implement something similar to dput meta profiles for dcut. We need a dcut "profile" which enableѕ or disables the command registries of actual dcut sub-commands at runtime, depending on a configurable key. Then we could define sub-commands which should (not) be enabled by default. tests/* ------- - get 80% coverage - test ftp / sftp w/mockers (pyftpdlib) uploader -------- - A progress bar feature requests ---------------- - real upload progress (also ensure local buffer checks for ftp don't throw it off) - ftp control connections die without a keepalive, ensure that's right - check for ppa for uploads to !(ppa) - upload to more then one ppa - sftp upload bar processors ---------- - implement the dinstall switch as a processor (possibly!) dput-info --------- - Implement various switches (/-p/-v/-V) (from dput originally) schema validation ----------------- - make fqdn a required key again, once we can ignore schema validation for DEFAULT.json Paul's Wishlist --------------- Python 3.x support. dput-ng-1.7/examples/0000755000000000000000000000000012114414240011434 5ustar dput-ng-1.7/examples/hooks/0000755000000000000000000000000012114414240012557 5ustar dput-ng-1.7/examples/hooks/clojure-arno-tester/0000755000000000000000000000000012114414240016463 5ustar dput-ng-1.7/examples/hooks/clojure-arno-tester/skell/0000755000000000000000000000000012114414240017575 5ustar dput-ng-1.7/examples/hooks/clojure-arno-tester/skell/hooks/0000755000000000000000000000000012114414240020720 5ustar dput-ng-1.7/examples/hooks/clojure-arno-tester/skell/hooks/clojtest.json0000644000000000000000000000017012114414240023440 0ustar { "description": "Make sure Arno doesn't maintain anything", "path": "clojtest.dput-checker", "pre": true } dput-ng-1.7/examples/hooks/clojure-arno-tester/code/0000755000000000000000000000000012114414240017375 5ustar dput-ng-1.7/examples/hooks/clojure-arno-tester/code/clojtest.clj0000644000000000000000000000075712114414240021727 0ustar ; Copyright (c) Paul R. Tagliamonte , 2012, under the ; terms of dput-ng it's self. (ns clojtest (:require dput.core dput.exceptions)) (defn log [x] ; for debug output (.debug dput.core/logger x)) (defn dput-checker [changes profile interface] (cond (>= (-> changes (.get "maintainer") (.find "arno@debian.org")) 0) (throw (dput.exceptions/HookException. "Maintainer's Arno. Aborting upload")) :else (log "Nah, it's not arno, we're good"))) dput-ng-1.7/examples/hooks/twitter/0000755000000000000000000000000012240211072014257 5ustar dput-ng-1.7/examples/hooks/twitter/README.Installing0000644000000000000000000000544012240211072017245 0ustar How to install dput-ng's twitter bits: ====================================== Before we begin, you should be sure to install the twitter dput-ng plugin. Here's the quick version for 1.4+ (versions before this didn't include the script in examples, please fetch the files from git if you're unable to use a recent dput-ng version). If it's not, remember to even install the python-twitter package. It's mandatory to have this hook working correctly. mkdir -p ~/.dput.d/{scripts,hooks} ln -s /usr/share/doc/dput-ng/examples/hooks/twitter/code/tweet.py \ ~/.dput.d/scripts/tweet.py ln -s /usr/share/doc/dput-ng/examples/hooks/twitter/skel/tweet.json \ ~/.dput.d/hooks/tweet.json So, right now, setting this plugin up is a *bit* of a pain for two reasons. 1) Twitter refuses to allow apps to ship secret key / id, which means that Twitter expects you (yes you, the end user) to register for an Application. They insist (otherwise) on a "best-effort" to hide the keys, so in the future, I may include something like: key = "FCi6UeIMwXxAIiZVkd19t".decode('rot-13') secret = "4m8LFfivMlVeqCzQNLIhj2EgqjkAAg3e49KbUIeZ".decode('rot-13') 2) We don't have a script to get a user token (etc), so even if we did ship a script to generate the oath_secret / oath_token. Contributions to this are uber welcome. HowTo: ====== We need to register for a twitter app. Go to: https://dev.twitter.com/apps/new name: paultag-dput-ng # replace paultag with your name description: dput: The Next Generation website: http://dput-ng.debian.net [x] Yes, I agree! > nonsense CAPtCHA1 [Create your Twitter Application] Now that the app is created, you should be on the /apps/show screen. Go to the [Settings] tab, and go to the "Application Type" pane. Change "Read-only" to "Read and Write", then click on: [Update this Twitter application's settings] Go back to the [Details] tab, go to the bottom and click on: [Create my Access Token] You should now see "Your access token" It'll have the following in the table: Access token: [token] Access token secret: [secret] Access level: Read and write OK, now that we have all the information: Now, let's drop down the config: cat < ~/.twitter.json { "consumer_key": "", "consumer_secret": "", "oath_secret": "", "oath_token": "" } EOF Now: $ sensible-editor ~/.twitter.json Edit and add your information. Consumer Key / Secret --> consumer_key/secret Access token --> oauth_token Access secret --> oauth_secret Finally, we add the hook to targets we want: $ if [ ! -d ~/.dput.d/profiles/ ]; then mkdir -p ~/.dput.d/profiles/; fi $ cat < ~/.dput.d/profiles/ftp-master.json { "+hooks": [ "tweet" ] } EOF And there we are! Sorry it's so painful! Fixes to come, I'm sure! dput-ng-1.7/examples/hooks/twitter/skel/0000755000000000000000000000000012114414240015217 5ustar dput-ng-1.7/examples/hooks/twitter/skel/tweet.json0000644000000000000000000000013512114414240017241 0ustar { "description": "Tweet after an upload.", "path": "tweet.tweet", "post": true } dput-ng-1.7/examples/hooks/twitter/code/0000755000000000000000000000000012114414240015173 5ustar dput-ng-1.7/examples/hooks/twitter/code/tweet.py0000644000000000000000000000136512114414240016702 0ustar # Copyright (c) Paul Tagliamonte, 2012, under the terms of dput-ng. # Example: https://twitter.com/paultag/status/257981606139133954 import twitter import json import os def tweet(changes, profile, interface): tweet = "I've just uploaded %s/%s to %s's %s suite #debian" % ( changes['Source'], changes['Version'], profile['name'], changes['Distribution'] ) if len(tweet) > 140: tweet = tweet[:140] obj = json.load(open(os.path.expanduser("~/.twitter.json"), 'r')) t = twitter.Api( consumer_key=obj['consumer_key'], consumer_secret=obj['consumer_secret'], access_token_key=obj['oath_token'], access_token_secret=obj['oath_secret'] ) t.PostUpdates(tweet) dput-ng-1.7/examples/hooks/bd-blacklist/0000755000000000000000000000000012114414240015112 5ustar dput-ng-1.7/examples/hooks/bd-blacklist/skell/0000755000000000000000000000000012114414240016224 5ustar dput-ng-1.7/examples/hooks/bd-blacklist/skell/hooks/0000755000000000000000000000000012114414240017347 5ustar dput-ng-1.7/examples/hooks/bd-blacklist/skell/hooks/bd-blacklist.json0000644000000000000000000000021412114414240022572 0ustar { "description": "Blacklist certain (configurable) build-dependencies", "path": "bd-blacklist.blacklist-checker", "pre": true } dput-ng-1.7/examples/hooks/bd-blacklist/code/0000755000000000000000000000000012114414240016024 5ustar dput-ng-1.7/examples/hooks/bd-blacklist/code/bd-blacklist.clj0000644000000000000000000000244412114414240021055 0ustar ; Copyright (c) Gergely Nagy , 2012, under the ; terms of dput-ng itself. (ns bd-blacklist (:require dput.core dput.exceptions dput.dsc)) (defn prune-build-deps "Prune a string representation of the build-depends so that only a list of packages remain." [bd-string] (map #(first (-> % (.strip) (.split " "))) (.. bd-string (split ",")))) (defn has-blacklisted? "Given a dsc file and a blacklist, check if any of the build-depencencies are in that list. Throws an error if there are matches." [dsc-file blacklist] (let [dsc (dput.dsc/parse_dsc_file dsc-file) build-deps (prune-build-deps (.. dsc (get "build-depends")))] (if-let [bad-bd (some blacklist build-deps)] (throw (dput.exceptions/HookException. (str "Blacklisted build-dependency found: " bad-bd))) (-> dput.core/logger (.trace "Build-Dependencies do not have anything on the blacklist"))))) (defn blacklist-checker "Checks whether the dsc has blacklisted build-dependencies, ignores the check when no dsc is to be found." [changes profile interface] (if-let [dsc-file (.. changes (get_dsc))] (has-blacklisted? dsc-file (set (get profile "bd-blacklist"))) (-> dput.core/logger (.trace "No .dsc found, build-dependencies cannot be checked")))) dput-ng-1.7/examples/external-invocation/0000755000000000000000000000000012114414240015425 5ustar dput-ng-1.7/examples/external-invocation/easy_upload.py0000644000000000000000000000020312114414240020277 0ustar #!/usr/bin/env python # This file is literally not copyrightable. from dput import upload import sys upload(sys.argv[1], 'test') dput-ng-1.7/README.md0000644000000000000000000000064512114414240011102 0ustar dput-ng ------- dput-ng is a from-scratch refresh of the old `dput(1)` command. This tool is used (at some point) to aid with the act of uploading a package to an archive. dput-ng aims to be compatible with the old `dput(1)` config files, while adding additional features for better sanity checking, and designed in an extensible way. Check the [docs](http://dput.rtfd.org) for more on topics that might interest you. dput-ng-1.7/AUTHORS0000644000000000000000000000165612240211072010674 0ustar Authors of dput-ng ================== The following list is the list of every contributor to dput-ng, in the order that their patches were accepted into dput-ng. If you modify dput-ng, please add yourself to this list, on the *first* commit. Git also has a nice way to view these same names: > git shortlog -sm | sed -r 's/[0-9]+//g' | sed 's/^[ \t]*//' Author List =========== Arno Töll Paul Tagliamonte Michael Gilbert Luke Faraone Bernhard R. Link Sandro Tosi Ansgar Burchardt Salvatore Bonaccorso Gergely Nagy Luca Falavigna Wolodja Wentland Sebastian Ramacher Thomas Preud'homme Stefano Rivera Matteo F. Vescovi dput-ng-1.7/.pydevproject0000644000000000000000000000063512114414240012341 0ustar /dput-ng python 2.7 Default dput-ng-1.7/LICENSE0000644000000000000000000004325412114414240010633 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. dput-ng-1.7/skel/0000755000000000000000000000000012213137652010565 5ustar dput-ng-1.7/skel/hooks/0000755000000000000000000000000012215340272011704 5ustar dput-ng-1.7/skel/hooks/gpg.json0000644000000000000000000000020712160717773013370 0ustar { "description": "check GnuPG signatures before the upload", "path": "dput.hooks.gpg.check_gpg_signature", "pre": true } dput-ng-1.7/skel/hooks/protected-distribution.json0000644000000000000000000000026712160717773017327 0ustar { "description": "warn before uploading to distributions where a special policy applies", "path": "dput.hooks.distribution.check_protected_distributions", "pre": true } dput-ng-1.7/skel/hooks/impatient.json0000644000000000000000000000021212160717773014601 0ustar { "description": "Tell me when dinstall will pick this up", "path": "dput.hooks.impatient.check_next_install", "post": true } dput-ng-1.7/skel/hooks/supported-distribution.json0000644000000000000000000000030712215340272017341 0ustar { "description": "check whether the target distribution is currently supported (using distro-info)", "path": "dput.hooks.distro_info_checks.check_supported_distribution", "pre": true } dput-ng-1.7/skel/hooks/suite-mismatch.json0000644000000000000000000000023612160717773015551 0ustar { "description": "check the target distribution for common errors", "path": "dput.hooks.distribution.check_distribution_matches", "pre": true } dput-ng-1.7/skel/hooks/lintian.json0000644000000000000000000000016512114414240014232 0ustar { "description": "runs lintian before the upload", "path": "dput.hooks.lintian.lintian", "pre": true } dput-ng-1.7/skel/hooks/check-debs.json0000644000000000000000000000021712160717773014604 0ustar { "description": "makes sure the upload contains a binary package", "path": "dput.hooks.deb.check_debs_in_upload", "pre": true } dput-ng-1.7/skel/hooks/checksum.json0000644000000000000000000000020412160717773014412 0ustar { "description": "verify checksums before uploading", "path": "dput.hooks.checksum.validate_checksums", "pre": true } dput-ng-1.7/skel/hooks/allowed-distribution.json0000644000000000000000000000026712160717773016765 0ustar { "description": "check whether a local profile permits uploads to the target distribution", "path": "dput.hooks.distribution.check_allowed_distribution", "pre": true } dput-ng-1.7/skel/metas/0000755000000000000000000000000012215340272011672 5ustar dput-ng-1.7/skel/metas/ubuntu.json0000644000000000000000000000045112215340272014107 0ustar { "allow_dcut": false, "check-debs": { "enforce": "source", "skip": false }, "codenames": "ubuntu", "hooks": [ "supported-distribution", "checksum", "suite-mismatch", "check-debs", "gpg" ], "valid_commands": [] } dput-ng-1.7/skel/metas/boring.json0000644000000000000000000000030512213137264014046 0ustar { "allow_dcut": false, "allowed-distribution": {}, "codenames": null, "hooks": [ "allowed-distribution", "checksum", "suite-mismatch", "gpg" ] } dput-ng-1.7/skel/metas/debomatic.json0000644000000000000000000000046112203760156014521 0ustar { "allow_dcut": true, "allow_debomatic_commands": true, "hooks": [ "allowed-distribution", "checksum", "suite-mismatch" ], "valid_commands": [ "debomatic-builddep", "debomatic-porter", "debomatic-rebuild", "debomatic-rm" ] } dput-ng-1.7/skel/metas/debian.json0000644000000000000000000000107112160717773014023 0ustar { "allow_dcut": true, "allowed-distribution": { "codename-groups": [ "general", "backport", "rm-managed" ] }, "check-debs": { "enforce": "debs", "skip": false }, "codenames": "debian", "hooks": [ "allowed-distribution", "protected-distribution", "checksum", "suite-mismatch", "check-debs", "gpg" ], "valid_commands": [ "break-the-archive", "cancel", "dm", "reschedule", "rm", "upload" ] } dput-ng-1.7/skel/commands/0000755000000000000000000000000012203760156012366 5ustar dput-ng-1.7/skel/commands/reschedule.json0000644000000000000000000000014612114414240015374 0ustar { "description": "reschedule command", "path": "dput.commands.reschedule.RescheduleCommand" } dput-ng-1.7/skel/commands/upload.json0000644000000000000000000000013212114414240014530 0ustar { "description": "upload command", "path": "dput.commands.upload.UploadCommand" } dput-ng-1.7/skel/commands/debomatic-rm.json0000644000000000000000000000015112160717773015632 0ustar { "description": "Deb-o-Matic rm command", "path": "dput.commands.contrib.debomatic.RmCommand" } dput-ng-1.7/skel/commands/dm.json0000644000000000000000000000011612114414240013646 0ustar { "description": "dm command", "path": "dput.commands.dm.DmCommand" } dput-ng-1.7/skel/commands/debomatic-rebuild.json0000644000000000000000000000016312160717773016645 0ustar { "description": "Deb-o-Matic rebuild command", "path": "dput.commands.contrib.debomatic.RebuildCommand" } dput-ng-1.7/skel/commands/rm.json0000644000000000000000000000011612114414240013664 0ustar { "description": "rm command", "path": "dput.commands.rm.RmCommand" } dput-ng-1.7/skel/commands/break-the-archive.json0000644000000000000000000000016712114414240016535 0ustar { "description": "break-the-archive command", "path": "dput.commands.breakthearchive.BreakTheArchiveCommand" } dput-ng-1.7/skel/commands/cancel.json0000644000000000000000000000013212114414240014471 0ustar { "description": "cancel command", "path": "dput.commands.cancel.CancelCommand" } dput-ng-1.7/skel/commands/debomatic-porter.json0000644000000000000000000000016112160717773016530 0ustar { "description": "Deb-o-Matic porter command", "path": "dput.commands.contrib.debomatic.PorterCommand" } dput-ng-1.7/skel/commands/debomatic-builddep.json0000644000000000000000000000016512203760156017000 0ustar { "description": "Deb-o-Matic builddep command", "path": "dput.commands.contrib.debomatic.BuilddepCommand" } dput-ng-1.7/skel/profiles/0000755000000000000000000000000012241260717012410 5ustar dput-ng-1.7/skel/profiles/ubuntu.json0000644000000000000000000000066712215340272014632 0ustar { "fqdn": "upload.ubuntu.com", "incoming": "/", "login": "anonymous", "meta": "ubuntu", "method": "ftp", "supported-distribution": { "allowed": [ "release", "proposed", "backports", "security" ], "known": [ "release", "proposed", "updates", "backports", "security" ] } } dput-ng-1.7/skel/profiles/DEFAULT.json0000644000000000000000000000063612215337734014401 0ustar { "allow_dcut": false, "allow_unsigned_uploads": false, "allowed_distributions": "(?!UNRELEASED)", "default_host_main": "", "full_upload_log": false, "hash": "sha1", "interface": "cli", "login": "*", "meta": "boring", "method": "ftp", "passive_ftp": true, "post_upload_command": "", "pre_upload_command": "", "run_lintian": false, "scp_compress": true } dput-ng-1.7/skel/profiles/ssh-upload.json0000644000000000000000000000023512157712466015373 0ustar { "fqdn": "ssh.upload.debian.org", "incoming": "/srv/upload.debian.org/UploadQueue/", "login": "*", "meta": "debian", "method": "sftp" } dput-ng-1.7/skel/profiles/ppa.json0000644000000000000000000000057412215340272014065 0ustar { "fqdn": "ppa.launchpad.net", "incoming": "~%(ppa)s/ubuntu", "login": "anonymous", "meta": "ubuntu", "method": "ftp", "supported-distribution": { "allowed": [ "release" ], "known": [ "release", "proposed", "updates", "backports", "security" ] } } dput-ng-1.7/skel/profiles/ftp-eu.json0000644000000000000000000000022512157712466014513 0ustar { "fqdn": "ftp.eu.upload.debian.org", "incoming": "/pub/UploadQueue/", "login": "anonymous", "meta": "debian", "method": "ftp" } dput-ng-1.7/skel/profiles/security-master.json0000644000000000000000000000040112160717773016447 0ustar { "allowed-distribution": { "codename-groups": [ "security" ] }, "fqdn": "security-master.debian.org", "incoming": "/pub/SecurityUploadQueue", "login": "anonymous", "meta": "debian", "method": "ftp" } dput-ng-1.7/skel/profiles/mentors.json0000644000000000000000000000035612160717773015007 0ustar { "-hooks": [ "check-debs", "allowed-distribution" ], "allowed_distributions": ".*", "fqdn": "mentors.debian.net", "incoming": ".", "login": "anonymous", "meta": "debian", "method": "ftp" } dput-ng-1.7/skel/profiles/ftp-master.json0000644000000000000000000000022212157712466015372 0ustar { "fqdn": "ftp.upload.debian.org", "incoming": "/pub/UploadQueue/", "login": "anonymous", "meta": "debian", "method": "ftp" } dput-ng-1.7/skel/profiles/backports.json0000644000000000000000000000037312160717773015307 0ustar { "allowed-distribution": { "codename-groups": [ "backport" ] }, "fqdn": "backports-master.debian.org", "incoming": "/pub/UploadQueue/", "login": "anonymous", "meta": "debian", "method": "ftp" } dput-ng-1.7/skel/codenames/0000755000000000000000000000000012215340272012517 5ustar dput-ng-1.7/skel/codenames/debian.json0000644000000000000000000000120712160717773014651 0ustar { "backport": [ "stable-backports", "oldstable-backports", "wheezy-backports", "squeeze-backports" ], "general": [ "unstable", "experimental", "rc-buggy", "sid" ], "rm-managed": [ "stable", "oldstable", "wheezy", "squeeze", "testing-proposed-updates", "stable-proposed-updates", "oldstable-proposed-updates" ], "security": [ "testing-security", "stable-security", "oldstable-security", "squeeze-security", "wheezy-security", "jessie-security" ] } dput-ng-1.7/skel/interfaces/0000755000000000000000000000000012114414240012677 5ustar dput-ng-1.7/skel/interfaces/cli.json0000644000000000000000000000012612114414240014340 0ustar { "description": "cli interface", "path": "dput.interfaces.cli.CLInterface" } dput-ng-1.7/skel/README0000644000000000000000000000161612160717773011462 0ustar Howdy! Firstly, thanks for trying out dput-ng. Here's what's up with these subdirs: Interesting stuff: - metas: meta-classes for config files. - profiles: where you set upload targets (ftp-master, etc) - scripts: added to sys.path, for local plugins (etc) Boring stuff: - hooks: where hook routines register themselves. - commands: where dcut routines register themselves. - interfaces: where user interface routines register themselves. - uploaders: where upload routines register themselves. - codenames: list of codenames for things. - schemas: where the schemas for the conffiles are loaded (/usr/share only) You should read through: - http://dput.readthedocs.org/en/latest/reference/configs.html or, if you have the dput-ng-doc package installed: - file:///usr/share/doc/dput-ng/html/reference/configs.html Have fun! paultag & arno dput-ng-1.7/skel/schemas/0000755000000000000000000000000012160717773012221 5ustar dput-ng-1.7/skel/schemas/plugin.json0000644000000000000000000000076312114414240014376 0ustar { "additionalProperties": false, "description": "dput-ng plugin def", "properties": { "description": { "required": true, "type": "string" }, "path": { "required": true, "type": "string" }, "post": { "required": false, "type": "boolean" }, "pre": { "required": false, "type": "boolean" } }, "type": "object" } dput-ng-1.7/skel/schemas/config.json0000644000000000000000000000373512160717773014371 0ustar { "description": "dput-ng configuration file", "properties": { "allow_dcut": { "required": false, "type": "boolean" }, "allow_unsigned_uploads": { "required": false, "type": "boolean" }, "allowed_distributions": { "required": false, "type": "string" }, "default_host_main": { "blank": true, "required": false, "type": "string" }, "distributions": { "required": false, "type": "string" }, "fqdn": { "required": false, "type": "string" }, "full_upload_log": { "required": false, "type": "boolean" }, "hash": { "required": false, "type": "string" }, "hooks": { "items": { "type": "string" }, "required": false, "type": "array" }, "incoming": { "blank": true, "required": true, "type": "string" }, "interface": { "required": false, "type": "string" }, "login": { "required": false, "type": "string" }, "meta": { "required": false, "type": "string" }, "method": { "required": true, "type": "string" }, "passive_ftp": { "required": false, "type": "boolean" }, "post_upload_command": { "blank": true, "required": false, "type": "string" }, "pre_upload_command": { "blank": true, "required": false, "type": "string" }, "run_lintian": { "required": false, "type": "boolean" } }, "type": "object" } dput-ng-1.7/skel/uploaders/0000755000000000000000000000000012160717773012574 5ustar dput-ng-1.7/skel/uploaders/secure_sftp.json0000644000000000000000000000014512160717773016011 0ustar { "description": "secure sftp uploader", "path": "dput.uploaders.secure_sftp.SFTPUploader" } dput-ng-1.7/skel/uploaders/scp.json0000644000000000000000000000012412114414240014227 0ustar { "path": "dput.uploaders.scp.ScpUploader", "description": "scp uploader" } dput-ng-1.7/skel/uploaders/https.json0000777000000000000000000000000012114414240016456 2http.jsonustar dput-ng-1.7/skel/uploaders/ftp.json0000644000000000000000000000012412114414240014233 0ustar { "description": "ftp uploader", "path": "dput.uploaders.ftp.FtpUploader" } dput-ng-1.7/skel/uploaders/local.json0000644000000000000000000000013712114414240014540 0ustar { "description": "local file uploader", "path": "dput.uploaders.local.LocalUploader" } dput-ng-1.7/skel/uploaders/sftp.json0000644000000000000000000000012712114414240014421 0ustar { "description": "sftp uploader", "path": "dput.uploaders.sftp.SFTPUploader" } dput-ng-1.7/skel/uploaders/http.json0000644000000000000000000000013212114414240014420 0ustar { "description": "http(s) uploader", "path": "dput.uploaders.http.HTTPUploader" } dput-ng-1.7/setup.py0000755000000000000000000000144512171142463011347 0ustar #!/usr/bin/env python import re from dput import __appname__ from setuptools import setup long_description = open('README.md').read() cur = open('debian/changelog', 'r').readline().strip() pobj = re.findall( r'(?P.*) \((?P.*)\) (?P.*); .*', cur )[0] src, version, suite = pobj # Yes, I'm sorry, world. I'm sorry. setup( name=__appname__, version=version, packages=[ 'dput', 'dput.hooks', 'dput.configs', 'dput.commands', 'dput.commands.contrib', 'dput.uploaders', 'dput.interfaces', ], author="dput authors", author_email="paultag@debian.org", long_description=long_description, description='dput-ng -- like dput, but better', license="GPL-2+", url="", platforms=['any'] )