diff -Nru fabric-1.13.1/debian/changelog fabric-1.14.0/debian/changelog --- fabric-1.13.1/debian/changelog 2017-05-17 02:20:39.000000000 +0000 +++ fabric-1.14.0/debian/changelog 2017-12-26 01:55:31.000000000 +0000 @@ -1,10 +1,9 @@ -fabric (1.13.1-4) unstable; urgency=medium +fabric (1.14.0-1) unstable; urgency=medium - * debian/rules: Disable test_should_use_sentinel_for_tasks_that_errored - The test randomly fails in certain environments causing more - too much noise to be useful (Closes: #854686). + * New upstream release. + * Bump Standards-Version to 4.1.2, no changes. - -- Andrew Starr-Bochicchio Tue, 16 May 2017 22:20:39 -0400 + -- Andrew Starr-Bochicchio Mon, 25 Dec 2017 20:55:31 -0500 fabric (1.13.1-3) unstable; urgency=medium diff -Nru fabric-1.13.1/debian/control fabric-1.14.0/debian/control --- fabric-1.13.1/debian/control 2017-05-03 01:31:01.000000000 +0000 +++ fabric-1.14.0/debian/control 2017-12-25 23:36:46.000000000 +0000 @@ -12,7 +12,7 @@ python-paramiko, python-setuptools, python-sphinx, -Standards-Version: 3.9.8 +Standards-Version: 4.1.2 Vcs-Git: git://anonscm.debian.org/collab-maint/fabric.git Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/fabric.git Homepage: http://fabfile.org/ diff -Nru fabric-1.13.1/debian/patches/0001-Remove-Google-AdSense-tracker-from-docs.patch fabric-1.14.0/debian/patches/0001-Remove-Google-AdSense-tracker-from-docs.patch --- fabric-1.13.1/debian/patches/0001-Remove-Google-AdSense-tracker-from-docs.patch 2017-05-03 01:31:01.000000000 +0000 +++ fabric-1.14.0/debian/patches/0001-Remove-Google-AdSense-tracker-from-docs.patch 2017-12-26 01:09:36.000000000 +0000 @@ -8,8 +8,8 @@ Index: fabric/sites/shared_conf.py =================================================================== ---- fabric.orig/sites/shared_conf.py 2016-06-12 12:59:37.833456076 -0400 -+++ fabric/sites/shared_conf.py 2016-06-12 13:01:00.730947727 -0400 +--- fabric.orig/sites/shared_conf.py 2017-12-25 20:09:29.533079915 -0500 ++++ fabric/sites/shared_conf.py 2017-12-25 20:09:29.529079734 -0500 @@ -18,7 +18,7 @@ 'github_user': 'fabric', 'github_repo': 'fabric', diff -Nru fabric-1.13.1/debian/patches/0002-disable-failed-tests-with-fudge10.patch fabric-1.14.0/debian/patches/0002-disable-failed-tests-with-fudge10.patch --- fabric-1.13.1/debian/patches/0002-disable-failed-tests-with-fudge10.patch 2017-05-17 02:17:37.000000000 +0000 +++ fabric-1.14.0/debian/patches/0002-disable-failed-tests-with-fudge10.patch 2017-12-26 01:51:37.000000000 +0000 @@ -5,8 +5,8 @@ Index: fabric/setup.py =================================================================== ---- fabric.orig/setup.py 2016-12-24 13:33:23.324752988 -0500 -+++ fabric/setup.py 2016-12-24 13:33:23.324752988 -0500 +--- fabric.orig/setup.py 2017-12-25 20:50:56.296262849 -0500 ++++ fabric/setup.py 2017-12-25 20:50:56.292262661 -0500 @@ -44,7 +44,7 @@ url='http://fabfile.org', packages=find_packages(), @@ -18,8 +18,8 @@ 'console_scripts': [ Index: fabric/tests/test_utils.py =================================================================== ---- fabric.orig/tests/test_utils.py 2016-12-24 13:33:23.324752988 -0500 -+++ fabric/tests/test_utils.py 2016-12-24 13:33:23.324752988 -0500 +--- fabric.orig/tests/test_utils.py 2017-12-25 20:50:56.296262849 -0500 ++++ fabric/tests/test_utils.py 2017-12-25 20:50:56.292262661 -0500 @@ -17,16 +17,6 @@ assert_not_contains @@ -62,7 +62,7 @@ - # perform when they are allowed to bubble all the way to the top. So, we - # invoke a subprocess and look at its stderr instead. - with quiet(): -- result = local("fab -f tests/support/aborts.py kaboom", capture=True) +- result = local("python -m fabric.__main__ -f tests/support/aborts.py kaboom", capture=True) - # When error in #1318 is present, this has an extra "It burns!" at end of - # stderr string. - eq_(result.stderr, "Fatal error: It burns!\n\nAborting.") @@ -128,3 +128,42 @@ @mock_streams('stderr') @with_patched_object(utils, 'abort', Fake('abort', callable=True, +Index: fabric/tests/test_network.py +=================================================================== +--- fabric.orig/tests/test_network.py 2017-12-25 20:51:10.356922711 -0500 ++++ fabric/tests/test_network.py 2017-12-25 20:51:34.470052653 -0500 +@@ -232,34 +232,6 @@ + cache = HostConnectionCache() + cache[env.host_string] + +- @with_fakes +- @raises(NetworkError) +- def test_connect_does_not_prompt_password_when_ssh_raises_channel_exception(self): +- def raise_channel_exception_once(*args, **kwargs): +- if raise_channel_exception_once.should_raise_channel_exception: +- raise_channel_exception_once.should_raise_channel_exception = False +- raise ssh.ChannelException(2, 'Connect failed') +- raise_channel_exception_once.should_raise_channel_exception = True +- +- def generate_fake_client(): +- fake_client = Fake('SSHClient', allows_any_call=True, expect_call=True) +- fake_client.provides('connect').calls(raise_channel_exception_once) +- return fake_client +- +- fake_ssh = Fake('ssh', allows_any_call=True) +- fake_ssh.provides('SSHClient').calls(generate_fake_client) +- # We need the real exceptions here to preserve the inheritence structure +- fake_ssh.SSHException = ssh.SSHException +- fake_ssh.ChannelException = ssh.ChannelException +- patched_connect = patch_object('fabric.network', 'ssh', fake_ssh) +- patched_password = patch_object('fabric.network', 'prompt_for_password', Fake('prompt_for_password', callable = True).times_called(0)) +- try: +- connect('user', 'localhost', 22, HostConnectionCache()) +- finally: +- # Restore ssh +- patched_connect.restore() +- patched_password.restore() +- + + @mock_streams('stdout') + @server() diff -Nru fabric-1.13.1/debian/rules fabric-1.14.0/debian/rules --- fabric-1.13.1/debian/rules 2017-05-17 02:11:04.000000000 +0000 +++ fabric-1.14.0/debian/rules 2017-03-08 01:09:51.000000000 +0000 @@ -3,7 +3,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -export PYBUILD_TEST_ARGS=-v --exclude=test_nested_execution_with_explicit_ports --exclude=test_should_use_sentinel_for_tasks_that_errored +export PYBUILD_TEST_ARGS=-v --exclude=test_nested_execution_with_explicit_ports export PYBUILD_NAME=fabric %: diff -Nru fabric-1.13.1/fabric/contrib/files.py fabric-1.14.0/fabric/contrib/files.py --- fabric-1.13.1/fabric/contrib/files.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/contrib/files.py 2017-08-25 21:41:36.000000000 +0000 @@ -5,7 +5,6 @@ from __future__ import with_statement import hashlib -import re import os from StringIO import StringIO from functools import partial @@ -227,7 +226,7 @@ # Test the OS because of differences between sed versions with hide('running', 'stdout'): - platform = run("uname") + platform = run("uname", shell=False, pty=False) if platform in ('NetBSD', 'OpenBSD', 'QNX'): # Attempt to protect against failures/collisions hasher = hashlib.sha1() @@ -351,7 +350,7 @@ added.) The ``shell`` argument will be eventually passed to ``run/sudo``. See - description of the same argumnet in ``~fabric.contrib.sed`` for details. + description of the same argument in ``~fabric.contrib.sed`` for details. If ``case_sensitive`` is False, the `-i` flag will be passed to ``egrep``. @@ -433,14 +432,22 @@ def _escape_for_regex(text): """Escape ``text`` to allow literal matching using egrep""" - regex = re.escape(text) - # Seems like double escaping is needed for \ - regex = regex.replace('\\\\', '\\\\\\') - # Triple-escaping seems to be required for $ signs - regex = regex.replace(r'\$', r'\\\$') - # Whereas single quotes should not be escaped - regex = regex.replace(r"\'", "'") - return regex + re_specials = '\\^$|(){}[]*+?.' + sh_specials = '\\$`"' + re_chars = [] + sh_chars = [] + + for c in text: + if c in re_specials: + re_chars.append('\\') + re_chars.append(c) + + for c in re_chars: + if c in sh_specials: + sh_chars.append('\\') + sh_chars.append(c) + + return ''.join(sh_chars) def is_win(): """ diff -Nru fabric-1.13.1/fabric/contrib/project.py fabric-1.14.0/fabric/contrib/project.py --- fabric-1.13.1/fabric/contrib/project.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/contrib/project.py 2017-08-25 21:41:36.000000000 +0000 @@ -195,6 +195,7 @@ local_dir = local_dir.rstrip(os.sep) local_path, local_name = os.path.split(local_dir) + local_path = local_path or '.' tar_file = "%s.tar.gz" % local_name target_tar = os.path.join(remote_dir, tar_file) tmp_folder = mkdtemp() diff -Nru fabric-1.13.1/fabric/main.py fabric-1.14.0/fabric/main.py --- fabric-1.13.1/fabric/main.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/main.py 2017-08-25 21:41:36.000000000 +0000 @@ -75,9 +75,10 @@ """ Is the given path a Python package? """ + _exists = lambda s: os.path.exists(os.path.join(path, s)) return ( os.path.isdir(path) - and os.path.exists(os.path.join(path, '__init__.py')) + and (_exists('__init__.py') or _exists('__init__.pyc')) ) diff -Nru fabric-1.13.1/fabric/network.py fabric-1.14.0/fabric/network.py --- fabric-1.13.1/fabric/network.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/network.py 2017-08-25 21:41:36.000000000 +0000 @@ -472,7 +472,7 @@ sock=sock, ) for suffix in ('auth', 'deleg_creds', 'kex'): - name = 'gss_{0}'.format(suffix) + name = "gss_" + suffix val = env.get(name, None) if val is not None: kwargs[name] = val @@ -501,8 +501,12 @@ # If we get SSHExceptionError and the exception message indicates # SSH protocol banner read failures, assume it's caused by the # server load and try again. - if e.__class__ is ssh.SSHException \ - and msg == 'Error reading SSH protocol banner': + # + # If we are using a gateway, we will get a ChannelException if + # connection to the downstream host fails. We should retry. + if (e.__class__ is ssh.SSHException \ + and msg == 'Error reading SSH protocol banner') \ + or e.__class__ is ssh.ChannelException: if _tried_enough(tries): raise NetworkError(msg, e) continue diff -Nru fabric-1.13.1/fabric/sftp.py fabric-1.14.0/fabric/sftp.py --- fabric-1.13.1/fabric/sftp.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/sftp.py 2017-08-25 21:41:36.000000000 +0000 @@ -1,10 +1,10 @@ from __future__ import with_statement -import hashlib import os import posixpath import stat import re +import uuid from fnmatch import filter as fnfilter from fabric.state import output, connections, env @@ -158,11 +158,7 @@ # path in the default remote CWD (which, typically, the login user will # have write permissions on) in order to sudo(cp) it. if use_sudo: - target_path = remote_path - hasher = hashlib.sha1() - hasher.update(env.host_string) - hasher.update(target_path) - target_path = posixpath.join(temp_dir, hasher.hexdigest()) + target_path = posixpath.join(temp_dir, uuid.uuid4().hex) # Temporarily nuke 'cwd' so sudo() doesn't "cd" its mv command. # (The target path has already been cwd-ified elsewhere.) with settings(hide('everything'), cwd=""): @@ -250,10 +246,7 @@ # have write permissions on) in order to sudo(mv) it later. if use_sudo: target_path = remote_path - hasher = hashlib.sha1() - hasher.update(env.host_string) - hasher.update(target_path) - remote_path = posixpath.join(temp_dir, hasher.hexdigest()) + remote_path = posixpath.join(temp_dir, uuid.uuid4().hex) # Read, ensuring we handle file-like objects correct re: seek pointer putter = self.ftp.put if not local_is_path: diff -Nru fabric-1.13.1/fabric/state.py fabric-1.14.0/fabric/state.py --- fabric-1.13.1/fabric/state.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/state.py 2017-08-25 21:41:36.000000000 +0000 @@ -413,7 +413,18 @@ def _open_session(): - return connections[env.host_string].get_transport().open_session() + transport = connections[env.host_string].get_transport() + # Try passing session-open timeout for Paramiko versions which support it + # (1.14.3+) + try: + session = transport.open_session(timeout=env.timeout) + # Revert to old call behavior if we seem to have hit arity error. + # TODO: consider introspecting the exception to avoid masking other + # TypeErrors; but this is highly fragile, especially when taking i18n into + # account. + except TypeError: # Assume arity error + session = transport.open_session() + return session def default_channel(): diff -Nru fabric-1.13.1/fabric/version.py fabric-1.14.0/fabric/version.py --- fabric-1.13.1/fabric/version.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/fabric/version.py 2017-08-25 21:41:36.000000000 +0000 @@ -9,7 +9,7 @@ from os.path import abspath, dirname -VERSION = (1, 13, 1, 'final', 0) +VERSION = (1, 14, 0, 'final', 0) def git_sha(): diff -Nru fabric-1.13.1/LICENSE fabric-1.14.0/LICENSE --- fabric-1.13.1/LICENSE 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/LICENSE 2017-08-25 21:41:36.000000000 +0000 @@ -1,4 +1,4 @@ -Copyright (c) 2009-2016 Jeffrey E. Forcier +Copyright (c) 2009-2017 Jeffrey E. Forcier Copyright (c) 2008-2009 Christian Vest Hansen All rights reserved. diff -Nru fabric-1.13.1/setup.py fabric-1.14.0/setup.py --- fabric-1.13.1/setup.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/setup.py 2017-08-25 21:41:36.000000000 +0000 @@ -61,6 +61,7 @@ 'Operating System :: Unix', 'Operating System :: POSIX', 'Programming Language :: Python', + 'Programming Language :: Python :: 2 :: Only', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', diff -Nru fabric-1.13.1/sites/docs/usage/env.rst fabric-1.14.0/sites/docs/usage/env.rst --- fabric-1.13.1/sites/docs/usage/env.rst 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/sites/docs/usage/env.rst 2017-08-25 21:41:36.000000000 +0000 @@ -515,6 +515,19 @@ .. versionadded:: 0.9.1 .. seealso:: :option:`-k` +.. _output_prefix: + +``output_prefix`` +----------------- + +**Default:** ``True`` + +By default Fabric prefixes every line of output with either ``[hostname] out:`` +or ``[hostname] err:``. Those prefixes may be hidden by setting +``env.output_prefix`` to ``False``. + +.. versionadded:: 1.0.0 + .. _env-parallel: ``parallel`` diff -Nru fabric-1.13.1/sites/docs/usage/output_controls.rst fabric-1.14.0/sites/docs/usage/output_controls.rst --- fabric-1.13.1/sites/docs/usage/output_controls.rst 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/sites/docs/usage/output_controls.rst 2017-08-25 21:41:36.000000000 +0000 @@ -160,3 +160,10 @@ :option:`--show` arguments to :doc:`fab`, which behave exactly like the context managers of the same names (but are, naturally, globally applied) and take comma-separated strings as input. + +Prefix output +============= + +By default Fabric prefixes every line of ouput with either ``[hostname] out:`` +or ``[hostname] err:``. Those prefixes may be hidden by setting +``env.output_prefix`` to ``False``. \ No newline at end of file diff -Nru fabric-1.13.1/sites/www/changelog.rst fabric-1.14.0/sites/www/changelog.rst --- fabric-1.13.1/sites/www/changelog.rst 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/sites/www/changelog.rst 2017-08-25 21:41:36.000000000 +0000 @@ -2,6 +2,56 @@ Changelog ========= +* :release:`1.14.0 <2017-08-25>` +* :feature:`1475` Honor ``env.timeout`` when opening new remote sessions (as + opposed to the initial overall connection, which already honored timeout + settings.) Thanks to ``@EugeniuZ`` for the report & ``@jrmsgit`` for the + first draft of the patch. + + .. note:: + This feature only works with Paramiko 1.14.3 and above; if your Paramiko + version is older, no timeout can be set, and the previous behavior will + occur instead. + +* :release:`1.13.2 <2017-04-24>` +* :release:`1.12.2 <2017-04-24>` +* :bug:`1542` (via :issue:`1543`) Catch Paramiko-level gateway connection + errors (``ChannelError``) when raising ``NetworkError``; this prevents an + issue where gateway related issues were being treated as authentication + errors. Thanks to Charlie Stanley for catch & patch. +* :bug:`1555` Multiple simultaneous `~fabric.operations.get` and/or + `~fabric.operations.put` with ``use_sudo=True`` and for the same remote host + and path could fail unnecessarily. Thanks ``@arnimarj`` for the report and + Pierce Lopez for the patch. +* :bug:`1427` (via :issue:`1428`) Locate ``.pyc`` files when searching for + fabfiles to load; previously we only used the presence of ``.py`` files to + determine whether loading should be attempted. Credit: Ray Chen. +* :bug:`1294` fix text escaping for `~fabric.contrib.files.contains` and + `~fabric.contrib.files.append` which would fail if the text contained e.g. + ``>``. Thanks to ``@ecksun`` for report & Pierce Lopez for the patch. +* :support:`1065 backported` Fix incorrect SSH config reference in the docs for + ``env.keepalive``; it corresponds to ``ServerAliveInterval``, not + ``ClientAliveInterval``. Credit: Harry Percival. +* :bug:`1574` `~fabric.contrib.project.upload_project` failed for folder in + current directory specified without any path separator. Thanks ``@aidanmelen`` + for the report and Pierce Lopez for the patch. +* :support:`1590 backported` Replace a reference to ``fab`` in a test + subprocess, to use the ``python -m `` style instead; this allows + ``python setup.py test`` to run the test suite without having Fabric already + installed. Thanks to ``@BenSturmfels`` for catch & patch. +* :support:`- backported` Backport :issue:`1462` to 1.12.x (was previously only + backported to 1.13.x.) +* :support:`1416 backported` Add explicit "Python 2 only" note to ``setup.py`` + trove classifiers to help signal that fact to various info-gathering tools. + Patch courtesy of Gavin Bisesi. +* :bug:`1526` Disable use of PTY and shell for a background command execution + within `contrib.sed `, preventing a small class of + issues on some platforms/environments. Thanks to ``@doflink`` for the report + and Pierce Lopez for the final patch. +* :support:`1539 backported` Add documentation for :ref:`env.output_prefix + `. Thanks ``@jphalip``. +* :bug:`1514` Compatibility with Python 2.5 was broken by using the ``format()`` + method of a string (only in 1.11+). Report by ``@pedrudehuere``. * :release:`1.13.1 <2016-12-09>` * :bug:`1462` Make a PyCrypto-specific import and method call optional to avoid ``ImportError`` problems under Paramiko 2.x. Thanks to Alex Gaynor for catch diff -Nru fabric-1.13.1/sites/www/installing.rst fabric-1.14.0/sites/www/installing.rst --- fabric-1.13.1/sites/www/installing.rst 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/sites/www/installing.rst 2017-08-25 21:41:36.000000000 +0000 @@ -31,12 +31,13 @@ Dependencies ============ -In order for Fabric's installation to succeed, you will need four primary pieces of software: +In order for Fabric's installation to succeed, you will need three primary pieces of software: * the Python programming language; * the ``setuptools`` packaging/installation library; -* the Python `Paramiko `_ SSH library; -* and Paramiko's dependency, the PyCrypto cryptography library. +* and the Python `Paramiko `_ SSH library. Paramiko's dependencies differ + significantly between the 1.x and 2.x releases. See the `Paramiko installation docs + `_ for more info. and, if using the :ref:`parallel execution mode `: diff -Nru fabric-1.13.1/tests/test_contrib.py fabric-1.14.0/tests/test_contrib.py --- fabric-1.13.1/tests/test_contrib.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_contrib.py 2017-08-25 21:41:36.000000000 +0000 @@ -73,7 +73,7 @@ assert result == False @server(responses={ - 'egrep "Include\\ other\\.conf" "$(echo /etc/apache2/apache2.conf)"': "Include other.conf" + r'egrep "Include other\\.conf" "$(echo /etc/apache2/apache2.conf)"': "Include other.conf" }) def test_contains_performs_case_sensitive_search(self): """ @@ -85,7 +85,7 @@ assert result == True @server(responses={ - 'egrep -i "include\ Other\.CONF" "$(echo /etc/apache2/apache2.conf)"': "Include other.conf" + r'egrep -i "include Other\\.CONF" "$(echo /etc/apache2/apache2.conf)"': "Include other.conf" }) def test_contains_performs_case_insensitive_search(self): """ diff -Nru fabric-1.13.1/tests/test_main.py fabric-1.14.0/tests/test_main.py --- fabric-1.13.1/tests/test_main.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_main.py 2017-08-25 21:41:36.000000000 +0000 @@ -3,6 +3,7 @@ import copy from functools import partial from operator import isMappingType +import os.path import sys from fudge import Fake, patched_context @@ -10,7 +11,7 @@ from fabric.decorators import hosts, roles, task from fabric.context_managers import settings -from fabric.main import (parse_arguments, _escape_split, +from fabric.main import (parse_arguments, _escape_split, find_fabfile, load_fabfile as _load_fabfile, list_commands, _task_names, COMMANDS_HEADER, NESTED_REMINDER) import fabric.state @@ -360,6 +361,33 @@ # +# Fabfile finding +# + +class TestFindFabfile(FabricTest): + """Test Fabric's fabfile discovery mechanism.""" + def test_find_fabfile_can_discovery_package(self): + """Fabric should be capable of loading a normal package.""" + path = self.mkfile("__init__.py", "") + name = os.path.dirname(path) + assert find_fabfile([name,]) is not None + + def test_find_fabfile_can_discovery_package_with_pyc_only(self): + """ + Fabric should be capable of loading a package with __init__.pyc only. + """ + path = self.mkfile("__init__.pyc", "") + name = os.path.dirname(path) + assert find_fabfile([name,]) is not None + + def test_find_fabfile_should_refuse_fake_package(self): + """Fabric should refuse to load a non-package directory.""" + path = self.mkfile("foo.py", "") + name = os.path.dirname(path) + assert find_fabfile([name,]) is None + + +# # Fabfile loading # diff -Nru fabric-1.13.1/tests/test_network.py fabric-1.14.0/tests/test_network.py --- fabric-1.13.1/tests/test_network.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_network.py 2017-08-25 21:41:36.000000000 +0000 @@ -2,13 +2,13 @@ import sys -from nose.tools import ok_ +from nose.tools import ok_, raises from fudge import (Fake, patch_object, with_patched_object, patched_context, with_fakes) from fabric.context_managers import settings, hide, show from fabric.network import (HostConnectionCache, join_host_strings, normalize, - denormalize, key_filenames, ssh, NetworkError) + denormalize, key_filenames, ssh, NetworkError, connect) from fabric.state import env, output, _get_system_username from fabric.operations import run, sudo, prompt from fabric.tasks import execute @@ -232,6 +232,34 @@ cache = HostConnectionCache() cache[env.host_string] + @with_fakes + @raises(NetworkError) + def test_connect_does_not_prompt_password_when_ssh_raises_channel_exception(self): + def raise_channel_exception_once(*args, **kwargs): + if raise_channel_exception_once.should_raise_channel_exception: + raise_channel_exception_once.should_raise_channel_exception = False + raise ssh.ChannelException(2, 'Connect failed') + raise_channel_exception_once.should_raise_channel_exception = True + + def generate_fake_client(): + fake_client = Fake('SSHClient', allows_any_call=True, expect_call=True) + fake_client.provides('connect').calls(raise_channel_exception_once) + return fake_client + + fake_ssh = Fake('ssh', allows_any_call=True) + fake_ssh.provides('SSHClient').calls(generate_fake_client) + # We need the real exceptions here to preserve the inheritence structure + fake_ssh.SSHException = ssh.SSHException + fake_ssh.ChannelException = ssh.ChannelException + patched_connect = patch_object('fabric.network', 'ssh', fake_ssh) + patched_password = patch_object('fabric.network', 'prompt_for_password', Fake('prompt_for_password', callable = True).times_called(0)) + try: + connect('user', 'localhost', 22, HostConnectionCache()) + finally: + # Restore ssh + patched_connect.restore() + patched_password.restore() + @mock_streams('stdout') @server() diff -Nru fabric-1.13.1/tests/test_operations.py fabric-1.14.0/tests/test_operations.py --- fabric-1.13.1/tests/test_operations.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_operations.py 2017-08-25 21:41:36.000000000 +0000 @@ -763,19 +763,16 @@ """ get(use_sudo=True) works by copying to a temporary path, downloading it and then removing it at the end """ - # the sha1 hash is the unique filename of the file being downloaded. sha1() - name = "229a29e5693876645e39de0cb0532e43ad73311a" fake_run = Fake('_run_command', callable=True, expect_call=True).with_matching_args( - 'cp -p "/etc/apache2/apache2.conf" "%s"' % name, True, True, None, + fudge_arg.startswith('cp -p "/etc/apache2/apache2.conf" "'), True, True, None ).next_call().with_matching_args( - 'chown username "%s"' % name, True, True, None, + fudge_arg.startswith('chown username "'), True, True, None, ).next_call().with_matching_args( - 'chmod 400 "%s"' % name, True, True, None, + fudge_arg.startswith('chmod 400 "'), True, True, None, ).next_call().with_matching_args( - 'rm -f "%s"' % name, True, True, None, + fudge_arg.startswith('rm -f "'), True, True, None, ) - fake_get = Fake('get', callable=True, expect_call=True).with_args( - name, fudge_arg.any_value()) + fake_get = Fake('get', callable=True, expect_call=True) with hide('everything'): with patched_context('fabric.operations', '_run_command', fake_run): @@ -788,21 +785,19 @@ @with_fakes def test_get_use_sudo_temp_dir(self): """ - get(use_sudo=True, temp_dir="/tmp") works by copying to a /tmp/sha1_hash, downloading it and then removing it at the end + get(use_sudo=True, temp_dir="/tmp") works by copying to /tmp/..., downloading it and then removing it at the end """ - # the sha1 hash is the unique filename of the file being downloaded. sha1() - name = "229a29e5693876645e39de0cb0532e43ad73311a" fake_run = Fake('_run_command', callable=True, expect_call=True).with_matching_args( - 'cp -p "/etc/apache2/apache2.conf" "/tmp/%s"' % name, True, True, None, + fudge_arg.startswith('cp -p "/etc/apache2/apache2.conf" "/tmp/'), True, True, None, ).next_call().with_matching_args( - 'chown username "/tmp/%s"' % name, True, True, None, + fudge_arg.startswith('chown username "/tmp/'), True, True, None, ).next_call().with_matching_args( - 'chmod 400 "/tmp/%s"' % name, True, True, None, + fudge_arg.startswith('chmod 400 "/tmp/'), True, True, None, ).next_call().with_matching_args( - 'rm -f "/tmp/%s"' % name, True, True, None, + fudge_arg.startswith('rm -f "/tmp/'), True, True, None, ) fake_get = Fake('get', callable=True, expect_call=True).with_args( - '/tmp/%s' % name, fudge_arg.any_value()) + fudge_arg.startswith('/tmp/'), fudge_arg.any_value()) with hide('everything'): with patched_context('fabric.operations', '_run_command', fake_run): @@ -963,12 +958,10 @@ """ put(use_sudo=True) works by uploading a the `local_path` to a temporary path and then moving it to a `remote_path` """ - # the sha1 hash is the unique filename of the file being downloaded. sha1() fake_run = Fake('_run_command', callable=True, expect_call=True).with_matching_args( - 'mv "7c91837ec0b3570264a325df6b7ef949ee22bc56" "/foobar.txt"', True, True, None, + fudge_arg.startswith('mv "'), True, True, None, ) - fake_put = Fake('put', callable=True, expect_call=True).with_args(fudge_arg.any_value(), - '7c91837ec0b3570264a325df6b7ef949ee22bc56') + fake_put = Fake('put', callable=True, expect_call=True) local_path = self.mkfile('foobar.txt', "baz") with hide('everything'): @@ -986,10 +979,9 @@ """ # the sha1 hash is the unique filename of the file being downloaded. sha1() fake_run = Fake('_run_command', callable=True, expect_call=True).with_matching_args( - 'mv "/tmp/7c91837ec0b3570264a325df6b7ef949ee22bc56" "/foobar.txt"', True, True, None, + fudge_arg.startswith('mv "'), True, True, None, ) - fake_put = Fake('put', callable=True, expect_call=True).with_args(fudge_arg.any_value(), - '/tmp/7c91837ec0b3570264a325df6b7ef949ee22bc56') + fake_put = Fake('put', callable=True, expect_call=True) local_path = self.mkfile('foobar.txt', "baz") with hide('everything'): diff -Nru fabric-1.13.1/tests/test_project.py fabric-1.14.0/tests/test_project.py --- fabric-1.13.1/tests/test_project.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_project.py 2017-08-25 21:41:36.000000000 +0000 @@ -104,7 +104,23 @@ # local() is called more than once so we need an extra next_call() # otherwise fudge compares the args to the last call to local() self.fake_local.with_args( - arg.endswith("-C %s %s" % os.path.split(project_path)) + arg.endswith("-C path/to/my project") + ).next_call() + + # Exercise + project.upload_project(local_dir=project_path) + + + @fudge.with_fakes + def test_path_to_local_project_no_separator(self): + """Local folder can have no path separator (in current directory).""" + + project_path = "testpath" + + # local() is called more than once so we need an extra next_call() + # otherwise fudge compares the args to the last call to local() + self.fake_local.with_args( + arg.endswith("-C . testpath") ).next_call() # Exercise diff -Nru fabric-1.13.1/tests/test_utils.py fabric-1.14.0/tests/test_utils.py --- fabric-1.13.1/tests/test_utils.py 2016-12-09 20:16:52.000000000 +0000 +++ fabric-1.14.0/tests/test_utils.py 2017-08-25 21:41:36.000000000 +0000 @@ -93,7 +93,7 @@ # perform when they are allowed to bubble all the way to the top. So, we # invoke a subprocess and look at its stderr instead. with quiet(): - result = local("fab -f tests/support/aborts.py kaboom", capture=True) + result = local("python -m fabric.__main__ -f tests/support/aborts.py kaboom", capture=True) # When error in #1318 is present, this has an extra "It burns!" at end of # stderr string. eq_(result.stderr, "Fatal error: It burns!\n\nAborting.")