Hello community,
here is the log from the commit of package python-Fabric for openSUSE:Factory checked in at 2019-04-19 18:38:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Fabric (Old)
and /work/SRC/openSUSE:Factory/.python-Fabric.new.5536 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Fabric"
Fri Apr 19 18:38:24 2019 rev:30 rq:693161 version:2.4.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Fabric/python-Fabric.changes 2018-08-24 17:03:46.150047551 +0200
+++ /work/SRC/openSUSE:Factory/.python-Fabric.new.5536/python-Fabric.changes 2019-04-19 18:38:26.191188786 +0200
@@ -1,0 +2,33 @@
+Tue Apr 9 06:20:04 UTC 2019 - John Paul Adrian Glaubitz
+
+- Version update to 2.4.0:
+ * [Feature] #1709: Add Group.close to allow closing an entire group’s
+ worth of connections at once. Patch via Johannes Löthberg.
+ * [Feature] #1780: Add context manager behavior to Group, to match
+ the same feature in Connection. Feature request by István Sárándi.
+ * [Feature] #1849: Add Connection.from_v1 (and Config.from_v1) for
+ easy creation of modern Connection/Config objects from the currently
+ configured Fabric 1.x environment. Should make upgrading piecemeal
+ much easier for many use cases.
+- additional changes from version 2.3.2:
+ * [Bug] #1852: Grant internal Connection objects created during
+ ProxyJump based gateways/proxies a copy of the outer Connection’s
+ configuration object. This was not previously done, which among
+ other things meant one could not fully disable SSH config file
+ loading (as the internal Connection objects would revert to the
+ default behavior). Thanks to Chris Adams for the report.
+ * [Bug]: Some debug logging was reusing Invoke’s logger object,
+ generating log messages “named” after invoke instead of fabric.
+ This has been fixed by using Fabric’s own logger everywhere instead.
+ * [Bug] #1850: Skip over ProxyJump configuration directives in SSH
+ config data when they would cause self-referential RecursionError
+ (e.g. due to wildcard-using Host stanzas which include the jump
+ server itself). Reported by Chris Adams.
+ * [Bug]: Fix a bug preventing tab completion (using the Invoke-level
+ --complete flag) from completing task names correctly (behavior was
+ to act as if there were never any tasks present, even if there was
+ a valid fabfile nearby).
+- Add sed expresion to spec file to remove all vendoring from imports
+- Run testsuite using the new %pytest macro
+
+-------------------------------------------------------------------
Old:
----
fabric-2.3.1.tar.gz
New:
----
fabric-2.4.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-Fabric.spec ++++++
--- /var/tmp/diff_new_pack.KRfiku/_old 2019-04-19 18:38:26.691189420 +0200
+++ /var/tmp/diff_new_pack.KRfiku/_new 2019-04-19 18:38:26.691189420 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-Fabric
#
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -12,13 +12,13 @@
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-Fabric
-Version: 2.3.1
+Version: 2.4.0
Release: 0
Summary: A Pythonic tool for remote execution and deployment
License: BSD-2-Clause
@@ -66,6 +66,8 @@
%prep
%setup -q -n fabric-%{version}
+# fix all imports:
+sed -i 's/from invoke.vendor\./from\ /' fabric/connection.py fabric/group.py integration/concurrency.py tests/config.py tests/transfer.py tests/_util.py tests/connection.py tests/runners.py
%build
%python_build
@@ -76,9 +78,7 @@
%python_clone -a %{buildroot}%{_bindir}/fab
%check
-%{python_expand export PYTHONPATH=%{buildroot}%{$python_sitelib}
-py.test-%{$python_bin_suffix} tests/
-}
+%pytest tests/
%post
%python_install_alternative fab
++++++ fabric-2.3.1.tar.gz -> fabric-2.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/PKG-INFO new/fabric-2.4.0/PKG-INFO
--- old/fabric-2.3.1/PKG-INFO 2018-08-09 04:12:30.000000000 +0200
+++ new/fabric-2.4.0/PKG-INFO 2018-09-14 00:29:24.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fabric
-Version: 2.3.1
+Version: 2.4.0
Summary: High level SSH command execution
Home-page: http://fabfile.org
Author: Jeff Forcier
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/_version.py new/fabric-2.4.0/fabric/_version.py
--- old/fabric-2.3.1/fabric/_version.py 2018-08-09 04:12:26.000000000 +0200
+++ new/fabric-2.4.0/fabric/_version.py 2018-09-14 00:29:20.000000000 +0200
@@ -1,2 +1,2 @@
-__version_info__ = (2, 3, 1)
+__version_info__ = (2, 4, 0)
__version__ = ".".join(map(str, __version_info__))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/config.py new/fabric-2.4.0/fabric/config.py
--- old/fabric-2.3.1/fabric/config.py 2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/fabric/config.py 2018-09-14 00:29:20.000000000 +0200
@@ -24,6 +24,8 @@
- it extends the API to account for loading ``ssh_config`` files (which are
stored as additional attributes and have no direct relation to the
regular config data/hierarchy.)
+ - it adds a new optional constructor, `from_v1`, which :ref:`generates
+ configuration data from Fabric 1 <from-v1>`.
Intended for use with `.Connection`, as using vanilla
`invoke.config.Config` objects would require users to manually define
@@ -36,6 +38,69 @@
prefix = "fabric"
+ @classmethod
+ def from_v1(cls, env, **kwargs):
+ """
+ Alternate constructor which uses Fabric 1's ``env`` dict for settings.
+
+ All keyword arguments besides ``env`` are passed unmolested into the
+ primary constructor, with the exception of ``overrides``, which is used
+ internally & will end up resembling the data from ``env`` with the
+ user-supplied overrides on top.
+
+ .. warning::
+ Because your own config overrides will win over data from ``env``,
+ make sure you only set values you *intend* to change from your v1
+ environment!
+
+ For details on exactly which ``env`` vars are imported and what they
+ become in the new API, please see :ref:`v1-env-var-imports`.
+
+ :param env:
+ An explicit Fabric 1 ``env`` dict (technically, any
+ ``fabric.utils._AttributeDict`` instance should work) to pull
+ configuration from.
+
+ .. versionadded:: 2.4
+ """
+ # TODO: automagic import, if we can find a way to test that
+ # Use overrides level (and preserve whatever the user may have given)
+ # TODO: we really do want arbitrary number of config levels, don't we?
+ # TODO: most of these need more care re: only filling in when they
+ # differ from the v1 default. As-is these won't overwrite runtime
+ # overrides (due to .setdefault) but they may still be filling in empty
+ # values to stomp on lower level config levels...
+ data = kwargs.pop("overrides", {})
+ # TODO: just use a dataproxy or defaultdict??
+ for subdict in ("connect_kwargs", "run", "sudo", "timeouts"):
+ data.setdefault(subdict, {})
+ # PTY use
+ data["run"].setdefault("pty", env.always_use_pty)
+ # Gateway
+ data.setdefault("gateway", env.gateway)
+ # Agent forwarding
+ data.setdefault("forward_agent", env.forward_agent)
+ # Key filename(s)
+ if env.key_filename is not None:
+ data["connect_kwargs"].setdefault("key_filename", env.key_filename)
+ # Load keys from agent?
+ data["connect_kwargs"].setdefault("allow_agent", not env.no_agent)
+ data.setdefault("ssh_config_path", env.ssh_config_path)
+ # Sudo password
+ data["sudo"].setdefault("password", env.sudo_password)
+ # Vanilla password (may be used for regular and/or sudo, depending)
+ passwd = env.password
+ data["connect_kwargs"].setdefault("password", passwd)
+ if not data["sudo"]["password"]:
+ data["sudo"]["password"] = passwd
+ data["sudo"].setdefault("prompt", env.sudo_prompt)
+ data["timeouts"].setdefault("connect", env.timeout)
+ data.setdefault("load_ssh_configs", env.use_ssh_config)
+ data["run"].setdefault("warn", env.warn_only)
+ # Put overrides back for real constructor and go
+ kwargs["overrides"] = data
+ return cls(**kwargs)
+
def __init__(self, *args, **kwargs):
"""
Creates a new Fabric-specific config object.
@@ -60,11 +125,12 @@
``~/.ssh/config``.
:param bool lazy:
- Has the same meaning as the parent class' ``lazy``, but additionall
- controls whether SSH config file loading is deferred (requires
- manually calling `load_ssh_config` sometime.) For example, one may
- need to wait for user input before calling `set_runtime_ssh_path`,
- which will inform exactly what `load_ssh_config` does.
+ Has the same meaning as the parent class' ``lazy``, but
+ additionally controls whether SSH config file loading is deferred
+ (requires manually calling `load_ssh_config` sometime.) For
+ example, one may need to wait for user input before calling
+ `set_runtime_ssh_path`, which will inform exactly what
+ `load_ssh_config` does.
"""
# Tease out our own kwargs.
# TODO: consider moving more stuff out of __init__ and into methods so
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/connection.py new/fabric-2.4.0/fabric/connection.py
--- old/fabric-2.3.1/fabric/connection.py 2018-08-09 04:09:28.000000000 +0200
+++ new/fabric-2.4.0/fabric/connection.py 2018-09-14 00:29:20.000000000 +0200
@@ -11,7 +11,6 @@
from six import string_types
import socket
-
from invoke import Context
from invoke.exceptions import ThreadException
from paramiko.agent import AgentRequestHandler
@@ -20,6 +19,7 @@
from paramiko.proxy import ProxyCommand
from .config import Config
+from .exceptions import InvalidV1Env
from .transfer import Transfer
from .tunnels import TunnelManager, Tunnel
@@ -30,6 +30,29 @@
return method(self, *args, **kwargs)
+def derive_shorthand(host_string):
+ user_hostport = host_string.rsplit("@", 1)
+ hostport = user_hostport.pop()
+ user = user_hostport[0] if user_hostport and user_hostport[0] else None
+
+ # IPv6: can't reliably tell where addr ends and port begins, so don't
+ # try (and don't bother adding special syntax either, user should avoid
+ # this situation by using port=).
+ if hostport.count(":") > 1:
+ host = hostport
+ port = None
+ # IPv4: can split on ':' reliably.
+ else:
+ host_port = hostport.rsplit(":", 1)
+ host = host_port.pop(0) or None
+ port = host_port[0] if host_port and host_port[0] else None
+
+ if port is not None:
+ port = int(port)
+
+ return {"user": user, "host": host, "port": port}
+
+
class Connection(Context):
"""
A connection to an SSH daemon, with methods for commands and file transfer.
@@ -47,12 +70,16 @@
`.Connection` has a basic "`create <__init__>`, `connect/open <open>`, `do
work <run>`, `disconnect/close <close>`" lifecycle:
- * `Instantiation <__init__>` imprints the object with its connection
+ - `Instantiation <__init__>` imprints the object with its connection
parameters (but does **not** actually initiate the network connection).
- * Methods like `run`, `get` etc automatically trigger a call to
+
+ - An alternate constructor exists for users :ref:`upgrading piecemeal
+ from Fabric 1 <from-v1>`: `from_v1`
+
+ - Methods like `run`, `get` etc automatically trigger a call to
`open` if the connection is not active; users may of course call `open`
manually if desired.
- * Connections do not always need to be explicitly closed; much of the
+ - Connections do not always need to be explicitly closed; much of the
time, Paramiko's garbage collection hooks or Python's own shutdown
sequence will take care of things. **However**, should you encounter edge
cases (for example, sessions hanging on exit) it's helpful to explicitly
@@ -113,6 +140,64 @@
_sftp = None
_agent_handler = None
+ @classmethod
+ def from_v1(cls, env, **kwargs):
+ """
+ Alternate constructor which uses Fabric 1's ``env`` dict for settings.
+
+ All keyword arguments besides ``env`` are passed unmolested into the
+ primary constructor.
+
+ .. warning::
+ Because your own config overrides will win over data from ``env``,
+ make sure you only set values you *intend* to change from your v1
+ environment!
+
+ For details on exactly which ``env`` vars are imported and what they
+ become in the new API, please see :ref:`v1-env-var-imports`.
+
+ :param env:
+ An explicit Fabric 1 ``env`` dict (technically, any
+ ``fabric.utils._AttributeDict`` instance should work) to pull
+ configuration from.
+
+ .. versionadded:: 2.4
+ """
+ # TODO: import fabric.state.env (need good way to test it first...)
+ # TODO: how to handle somebody accidentally calling this in a process
+ # where 'fabric' is fabric 2, and there's no fabric 1? Probably just a
+ # re-raise of ImportError??
+ # Our only requirement is a non-empty host_string
+ if not env.host_string:
+ raise InvalidV1Env(
+ "Supplied v1 env has an empty `host_string` value! Please make sure you're calling Connection.from_v1 within a connected Fabric 1 session." # noqa
+ )
+ # TODO: detect collisions with kwargs & except instead of overwriting?
+ # (More Zen of Python compliant, but also, effort, and also, makes it
+ # harder for users to intentionally overwrite!)
+ connect_kwargs = kwargs.setdefault("connect_kwargs", {})
+ kwargs.setdefault("host", env.host_string)
+ shorthand = derive_shorthand(env.host_string)
+ # TODO: don't we need to do the below skipping for user too?
+ kwargs.setdefault("user", env.user)
+ # Skip port if host string seemed to have it; otherwise we hit our own
+ # ambiguity clause in __init__. v1 would also have been doing this
+ # anyways (host string wins over other settings).
+ if not shorthand["port"]:
+ # Run port through int(); v1 inexplicably has a string default...
+ kwargs.setdefault("port", int(env.port))
+ # key_filename defaults to None in v1, but in v2, we expect it to be
+ # either unset, or set to a list. Thus, we only pull it over if it is
+ # not None.
+ if env.key_filename is not None:
+ connect_kwargs.setdefault("key_filename", env.key_filename)
+ # Obtain config values, if not given, from its own from_v1
+ # NOTE: not using setdefault as we truly only want to call
+ # Config.from_v1 when necessary.
+ if "config" not in kwargs:
+ kwargs["config"] = Config.from_v1(env)
+ return cls(**kwargs)
+
# TODO: should "reopening" an existing Connection object that has been
# closed, be allowed? (See e.g. how v1 detects closed/semi-closed
# connections & nukes them before creating a new client to the same host.)
@@ -311,37 +396,12 @@
#: The network port to connect on.
self.port = port or int(self.ssh_config.get("port", self.config.port))
- # Non-None values - string, Connection, even eg False - get set
- # directly; None triggers seek in config/ssh_config
- if gateway is None:
- # SSH config wins over Invoke-style config
- if "proxyjump" in self.ssh_config:
- # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
- # with the final (itself non-gatewayed) hop and work up to
- # the front (actual, supplied as our own gateway) hop
- hops = reversed(self.ssh_config["proxyjump"].split(","))
- prev_gw = None
- for hop in hops:
- # Happily, ProxyJump uses identical format to our host
- # shorthand...
- if prev_gw is None:
- # TODO: this isn't persisting config! which among other
- # things can lead to not honoring skipping ssh config
- # file loads...
- cxn = Connection(hop)
- else:
- cxn = Connection(hop, gateway=prev_gw)
- prev_gw = cxn
- gateway = prev_gw
- elif "proxycommand" in self.ssh_config:
- # Just a string, which we interpret as a proxy command..
- gateway = self.ssh_config["proxycommand"]
- else:
- # Neither of those? Our config value please.
- gateway = self.config.gateway
+ # Gateway/proxy/bastion/jump setting: non-None values - string,
+ # Connection, even eg False - get set directly; None triggers seek in
+ # config/ssh_config
#: The gateway `.Connection` or ``ProxyCommand`` string to be used,
#: if any.
- self.gateway = gateway
+ self.gateway = gateway if gateway is not None else self.get_gateway()
# NOTE: we use string above, vs ProxyCommand obj, to avoid spinning up
# the ProxyCommand subprocess at init time, vs open() time.
# TODO: make paramiko.proxy.ProxyCommand lazy instead?
@@ -414,6 +474,36 @@
return connect_kwargs
+ def get_gateway(self):
+ # SSH config wins over Invoke-style config
+ if "proxyjump" in self.ssh_config:
+ # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
+ # with the final (itself non-gatewayed) hop and work up to
+ # the front (actual, supplied as our own gateway) hop
+ hops = reversed(self.ssh_config["proxyjump"].split(","))
+ prev_gw = None
+ for hop in hops:
+ # Short-circuit if we appear to be our own proxy, which would
+ # be a RecursionError. Implies SSH config wildcards.
+ # TODO: in an ideal world we'd check user/port too in case they
+ # differ, but...seriously? They can file a PR with those extra
+ # half dozen test cases in play, E_NOTIME
+ if self.derive_shorthand(hop)["host"] == self.host:
+ return None
+ # Happily, ProxyJump uses identical format to our host
+ # shorthand...
+ kwargs = dict(config=self.config.clone())
+ if prev_gw is not None:
+ kwargs["gateway"] = prev_gw
+ cxn = Connection(hop, **kwargs)
+ prev_gw = cxn
+ return prev_gw
+ elif "proxycommand" in self.ssh_config:
+ # Just a string, which we interpret as a proxy command..
+ return self.ssh_config["proxycommand"]
+ # Fallback: config value (may be None).
+ return self.config.gateway
+
def __repr__(self):
# Host comes first as it's the most common differentiator by far
bits = [("host", self.host)]
@@ -459,26 +549,10 @@
return hash(self._identity())
def derive_shorthand(self, host_string):
- user_hostport = host_string.rsplit("@", 1)
- hostport = user_hostport.pop()
- user = user_hostport[0] if user_hostport and user_hostport[0] else None
-
- # IPv6: can't reliably tell where addr ends and port begins, so don't
- # try (and don't bother adding special syntax either, user should avoid
- # this situation by using port=).
- if hostport.count(":") > 1:
- host = hostport
- port = None
- # IPv4: can split on ':' reliably.
- else:
- host_port = hostport.rsplit(":", 1)
- host = host_port.pop(0) or None
- port = host_port[0] if host_port and host_port[0] else None
-
- if port is not None:
- port = int(port)
-
- return {"user": user, "host": host, "port": port}
+ # NOTE: used to be defined inline; preserving API call for both
+ # backwards compatibility and because it seems plausible we may want to
+ # modify behavior later, using eg config or other attributes.
+ return derive_shorthand(host_string)
@property
def is_connected(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/exceptions.py new/fabric-2.4.0/fabric/exceptions.py
--- old/fabric-2.3.1/fabric/exceptions.py 2018-07-25 20:34:43.000000000 +0200
+++ new/fabric-2.4.0/fabric/exceptions.py 2018-09-14 00:29:20.000000000 +0200
@@ -16,3 +16,11 @@
#: been no errors. See its docstring (and that of `.Group`) for
#: details.
self.result = result
+
+
+class InvalidV1Env(Exception):
+ """
+ Raised when attempting to import a Fabric 1 ``env`` which is missing data.
+ """
+
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/executor.py new/fabric-2.4.0/fabric/executor.py
--- old/fabric-2.3.1/fabric/executor.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/executor.py 2018-09-14 00:28:41.000000000 +0200
@@ -1,9 +1,9 @@
import invoke
from invoke import Call, Task
-from invoke.util import debug
from .tasks import ConnectionCall
from .exceptions import NothingToDo
+from .util import debug
class Executor(invoke.Executor):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/group.py new/fabric-2.4.0/fabric/group.py
--- old/fabric-2.3.1/fabric/group.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/group.py 2018-09-14 00:29:20.000000000 +0200
@@ -55,7 +55,12 @@
<Connection host='notahost'>: gaierror(...),
}
+ As with `.Connection`, `.Group` objects may be used as context managers,
+ which will automatically `.close` the object on block exit.
+
.. versionadded:: 2.0
+ .. versionchanged:: 2.4
+ Added context manager behavior.
"""
def __init__(self, *hosts, **kwargs):
@@ -130,8 +135,6 @@
# would be distinct from Group. (May want to switch Group to use that,
# though, whatever it ends up being?)
- # TODO: mirror Connection's close()?
-
def get(self, *args, **kwargs):
"""
Executes `.Connection.get` on all member `Connections <.Connection>`.
@@ -144,6 +147,21 @@
# TODO: actually implement on subclasses
raise NotImplementedError
+ def close(self):
+ """
+ Executes `.Connection.close` on all member `Connections <.Connection>`.
+
+ .. versionadded:: 2.4
+ """
+ for cxn in self:
+ cxn.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc):
+ self.close()
+
class SerialGroup(Group):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/main.py new/fabric-2.4.0/fabric/main.py
--- old/fabric-2.3.1/fabric/main.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/main.py 2018-09-14 00:28:41.000000000 +0200
@@ -54,7 +54,16 @@
@property
def _remainder_only(self):
- return not self.core.unparsed and self.core.remainder
+ # No 'unparsed' (i.e. tokens intended for task contexts), and remainder
+ # (text after a double-dash) implies a contextless/taskless remainder
+ # execution of the style 'fab -H host -- command'.
+ # NOTE: must ALSO check to ensure the double dash isn't being used for
+ # tab completion machinery...
+ return (
+ not self.core.unparsed
+ and self.core.remainder
+ and not self.args.complete.value
+ )
def load_collection(self):
# Stick in a dummy Collection if it looks like we were invoked w/o any
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric/transfer.py new/fabric-2.4.0/fabric/transfer.py
--- old/fabric-2.3.1/fabric/transfer.py 2018-07-25 20:34:43.000000000 +0200
+++ new/fabric-2.4.0/fabric/transfer.py 2018-09-06 21:28:02.000000000 +0200
@@ -6,7 +6,7 @@
import posixpath
import stat
-from invoke.util import debug # TODO: actual logging! LOL
+from .util import debug # TODO: actual logging! LOL
# TODO: figure out best way to direct folks seeking rsync, to patchwork's rsync
# call (which needs updating to use invoke.run() & fab 2 connection methods,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric.egg-info/PKG-INFO new/fabric-2.4.0/fabric.egg-info/PKG-INFO
--- old/fabric-2.3.1/fabric.egg-info/PKG-INFO 2018-08-09 04:12:29.000000000 +0200
+++ new/fabric-2.4.0/fabric.egg-info/PKG-INFO 2018-09-14 00:29:24.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fabric
-Version: 2.3.1
+Version: 2.4.0
Summary: High level SSH command execution
Home-page: http://fabfile.org
Author: Jeff Forcier
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/fabric.egg-info/SOURCES.txt new/fabric-2.4.0/fabric.egg-info/SOURCES.txt
--- old/fabric-2.3.1/fabric.egg-info/SOURCES.txt 2018-08-09 04:12:29.000000000 +0200
+++ new/fabric-2.4.0/fabric.egg-info/SOURCES.txt 2018-09-14 00:29:24.000000000 +0200
@@ -91,6 +91,8 @@
tests/_support/ssh_config/overridden_hostname.conf
tests/_support/ssh_config/proxyjump.conf
tests/_support/ssh_config/proxyjump_multi.conf
+tests/_support/ssh_config/proxyjump_multi_recursive.conf
+tests/_support/ssh_config/proxyjump_recursive.conf
tests/_support/ssh_config/runtime.conf
tests/_support/ssh_config/runtime_identity.conf
tests/_support/ssh_config/system.conf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/sites/docs/concepts/configuration.rst new/fabric-2.4.0/sites/docs/concepts/configuration.rst
--- old/fabric-2.3.1/sites/docs/concepts/configuration.rst 2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/sites/docs/concepts/configuration.rst 2018-09-14 00:29:00.000000000 +0200
@@ -87,7 +87,7 @@
OpenSSH.)
- ``gateway``: Used as the default value of the ``gateway`` kwarg for
`.Connection`. May be any value accepted by that argument. Default: ``None``.
-- ``load_openssh_configs``: Whether to automatically seek out :ref:`SSH config
+- ``load_ssh_configs``: Whether to automatically seek out :ref:`SSH config
files <ssh-config>`. When ``False``, no automatic loading occurs. Default:
``True``.
- ``port``: TCP port number used by `.Connection` objects when not otherwise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/sites/docs/index.rst new/fabric-2.4.0/sites/docs/index.rst
--- old/fabric-2.3.1/sites/docs/index.rst 2018-07-02 23:24:48.000000000 +0200
+++ new/fabric-2.4.0/sites/docs/index.rst 2018-09-06 21:28:02.000000000 +0200
@@ -20,7 +20,7 @@
Upgrading from 1.x
------------------
-Looking to upgrade from Fabric 1.x? See our :doc:`detailed upgrade guide
+Looking to upgrade from Fabric 1.x? See our :ref:`detailed upgrade guide
<upgrading>` on the nonversioned main project site.
.. _concepts-docs:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/sites/www/changelog.rst new/fabric-2.4.0/sites/www/changelog.rst
--- old/fabric-2.3.1/sites/www/changelog.rst 2018-08-09 04:12:24.000000000 +0200
+++ new/fabric-2.4.0/sites/www/changelog.rst 2018-09-14 00:29:20.000000000 +0200
@@ -5,10 +5,42 @@
.. note::
Looking for the Fabric 1.x changelog? See :doc:`/changelog-v1`.
+- :release:`2.4.0 <2018-09-13>`
+- :release:`2.3.2 <2018-09-13>`
+- :release:`2.2.3 <2018-09-13>`
+- :release:`2.1.6 <2018-09-13>`
+- :release:`2.0.5 <2018-09-13>`
+- :feature:`1849` Add `Connection.from_v1
+ ` for easy creation of a modern
+ ``Connection`` object from the currently configured Fabric 1.x
+ environment. Should make upgrading piecemeal much easier for many use
+ cases.
+- :feature:`1780` Add context manager behavior to `~fabric.group.Group`, to
+ match the same feature in `~fabric.connection.Connection`. Feature request by
+ István Sárándi.
+- :feature:`1709` Add `Group.close ` to allow closing
+ an entire group's worth of connections at once. Patch via Johannes Löthberg.
+- :bug:`-` Fix a bug preventing tab completion (using the Invoke-level
+ ``--complete`` flag) from completing task names correctly (behavior was to
+ act as if there were never any tasks present, even if there was a valid
+ fabfile nearby).
+- :bug:`1850` Skip over ``ProxyJump`` configuration directives in SSH config
+ data when they would cause self-referential ``RecursionError`` (e.g. due to
+ wildcard-using ``Host`` stanzas which include the jump server itself).
+ Reported by Chris Adams.
+- :bug:`-` Some debug logging was reusing Invoke's logger object, generating
+ log messages "named" after ``invoke`` instead of ``fabric``. This has been
+ fixed by using Fabric's own logger everywhere instead.
+- :bug:`1852` Grant internal `~fabric.connection.Connection` objects created
+ during ``ProxyJump`` based gateways/proxies a copy of the outer
+ ``Connection``'s configuration object. This was not previously done, which
+ among other things meant one could not fully disable SSH config file loading
+ (as the internal ``Connection`` objects would revert to the default
+ behavior). Thanks to Chris Adams for the report.
- :release:`2.3.1 <2018-08-08>`
-- :bug:`-` Update the new functionality added for :issue:`1826` so it uses
- ``export``; without this, nontrivial shell invocations like ``command1 &&
- command2`` end up only applying the env vars to the first command.
+- :bug:`- (2.3+)` Update the new functionality added for :issue:`1826` so it
+ uses ``export``; without this, nontrivial shell invocations like ``command1
+ && command2`` end up only applying the env vars to the first command.
- :release:`2.3.0 <2018-08-08>`
- :feature:`1826` Add a new Boolean configuration and
`~fabric.connection.Connection` parameter, ``inline_ssh_env``, which (when
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/sites/www/faq.rst new/fabric-2.4.0/sites/www/faq.rst
--- old/fabric-2.3.1/sites/www/faq.rst 2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/sites/www/faq.rst 2018-09-14 00:29:20.000000000 +0200
@@ -19,8 +19,8 @@
.. _remote-env-vars-dont-work:
-Environment variables are not being set correctly on the remote end!
-====================================================================
+Explicitly set env variables are not being set correctly on the remote end!
+===========================================================================
If your attempts to set environment variables for things like `Connection.run
` appear to silently fail, you're almost
@@ -34,6 +34,51 @@
instead.
+The remote shell environment doesn't match interactive shells!
+==============================================================
+
+You may find environment variables (or the behavior they trigger) differ
+interactively vs scripted via Fabric. For example, a program that's on your
+``$PATH`` when you manually ``ssh`` in might not be visible when using
+`Connection.run `; or special per-program env
+vars such as those for Python, pip, Java etc are not taking effect; etc.
+
+The root cause of this is typically because the SSH server runs non-interactive
+commands via a very limited shell call: ``/path/to/shell -c "command"`` (for
+example, `OpenSSH
+https://github.com/fabric/fabric/issues/1519#issuecomment-411247228`_). Most
+shells, when run this way, are not considered to be either **interactive** or
+**login** shells; and this then impacts which startup files get loaded.
+
+Users typically only modify shell files related to interactive operation (such
+as ``~/.bash_profile`` or ``/etc/zshrc``); such changes do not take effect when
+the SSH server is running one-off commands.
+
+To work around this, consult your shell's documentation to see if it offers any
+non-login, non-interactive config files; for example, ``zsh`` lets you
+configure ``/etc/zshrc`` or ``~/.zshenv`` for this purpose.
+
+.. note::
+ ``bash`` does not appear to offer standard non-login/non-interactive
+ startup files, even in version 4. However, it may attempt to determine if
+ it's being run by a remote-execution daemon and will apparently source
+ ``~/.bashrc`` if so; check to see if this is the case on your target
+ systems.
+
+.. note::
+ Another workaround for ``bash`` users is to reply on its ``$BASH_ENV``
+ functionality, which names a file path as the startup file to load:
+
+ - configure your SSH server to ``AcceptEnv BASH_ENV``, so that you can
+ actually set that env var for the remote session at the top level (most
+ SSH servers disallow this method by default).
+ - decide which file this should be, though if you're already modifying
+ files like ``~/.bash_profile`` or ``~/.bashrc``, you may want to just
+ point at that exact path.
+ - set the Fabric configuration value ``run.env`` to aim at the above path,
+ e.g. ``{"BASH_ENV": "~/.bash_profile"}``.
+
+
.. _one-shell-per-command:
My (``cd``/``workon``/``export``/etc) calls don't seem to work!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/sites/www/upgrading.rst new/fabric-2.4.0/sites/www/upgrading.rst
--- old/fabric-2.3.1/sites/www/upgrading.rst 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/sites/www/upgrading.rst 2018-09-14 00:29:20.000000000 +0200
@@ -119,6 +119,122 @@
For details on how to obtain the ``fabric2`` version of the package, see
:ref:`installing-as-fabric2`.
+.. _from-v1:
+
+Creating ``Connection`` and/or ``Config`` objects from v1 settings
+------------------------------------------------------------------
+
+A common tactic when upgrading piecemeal is to generate modern Fabric objects
+whose contents match the current Fabric 1 environment. Whereas Fabric 1 stores
+*all* configuration (including the "current host") in a single place -- the
+``env`` object -- modern Fabric breaks things up into multiple (albeit
+composed) objects: `~fabric.connection.Connection` for per-connection
+parameters, and `~fabric.config.Config` for general settings and defaults.
+
+In most cases, you'll only need to generate a `~fabric.connection.Connection`
+object using the alternate class constructor `Connection.from_v1
+`, which should be fed your appropriate
+local ``fabric.api.env`` object; see its API docs for details.
+
+A contrived example::
+
+ from fabric.api import env, run
+ from fabric2 import Connection
+
+ env.host_string = "admin@myserver"
+ run("whoami") # v1
+ cxn = Connection.from_v1(env)
+ cxn.run("whoami") # v2+
+
+By default, this constructor calls another API member -- `Config.from_v1
+` -- internally on your behalf. Users who need
+tighter control over modern-style config options may opt to call that
+classmethod explicitly and hand their modified result into `Connection.from_v1
+`, which will cause the latter to skip
+any implicit config creation.
+
+.. _v1-env-var-imports:
+
+Mapping of v1 ``env`` vars to modern API members
+------------------------------------------------
+
+The ``env`` vars and how they map to `~fabric.connection.Connection` arguments
+or `~fabric.config.Config` values (when fed into the ``.from_v1`` constructors
+described above) are listed below.
+
+.. list-table::
+ :header-rows: 1
+
+ * - v1 ``env`` var
+ - v2+ usage (prefixed with the class it ends up in)
+
+ * - ``always_use_pty``
+ - Config: ``run.pty``.
+ * - ``forward_agent``
+ - Config: ``connect_kwargs.forward_agent``.
+ * - ``gateway``
+ - Config: ``gateway``.
+ * - ``host_string``
+ - Connection: ``host`` kwarg (which can handle host-string like values,
+ including user/port).
+ * - ``key``
+ - **Not supported**: Fabric 1 performed extra processing on this
+ (trying a bunch of key classes to instantiate) before
+ handing it into Paramiko; modern Fabric prefers to just let you handle
+ Paramiko-level parameters directly.
+
+ If you're filling your Fabric 1 ``key`` data from a file, we recommend
+ switching to ``key_filename`` instead, which is supported.
+
+ If you're loading key data from some other source as a string, you
+ should know what type of key your data is and manually instantiate it
+ instead, then supply it to the ``connect_kwargs`` parameter. For
+ example::
+
+ from io import StringIO # or 'from StringIO' on Python 2
+ from fabric.state import env
+ from fabric2 import Connection
+ from paramiko import RSAKey
+ from somewhere import load_my_key_string
+
+ pkey = RSAKey.from_private_key(StringIO(load_my_key_string()))
+ cxn = Connection.from_v1(env, connect_kwargs={"pkey": pkey})
+
+ * - ``key_filename``
+ - Config: ``connect_kwargs.key_filename``.
+ * - ``no_agent``
+ - Config: ``connect_kwargs.allow_agent`` (inverted).
+ * - ``password``
+ - Config: ``connect_kwargs.password``, as well as ``sudo.password``
+ **if and only if** the env's ``sudo_password`` (see below) is unset.
+ (This mimics how v1 uses this particular setting - in earlier versions
+ there was no ``sudo_password`` at all.)
+ * - ``port``
+ - Connection: ``port`` kwarg. Is casted to an integer due to Fabric 1's
+ default being a string value (which is not valid in v2).
+
+ .. note::
+ Since v1's ``port`` is used both for a default *and* to store the
+ current connection state, v2 uses it to fill in the Connection
+ only, and not the Config, on assumption that it will typically be
+ the current connection state.
+
+ * - ``ssh_config_path``
+ - Config: ``ssh_config_path``.
+ * - ``sudo_password``
+ - Config: ``sudo.password``.
+ * - ``sudo_prompt``
+ - Config: ``sudo.prompt``.
+ * - ``timeout``
+ - Config: ``timeouts.connection`` (because v1's ambiguously named
+ ``timeout`` setting was, in fact, for connection timeouts).
+ * - ``use_ssh_config``
+ - Config: ``load_ssh_configs``.
+ * - ``user``
+ - Connection: ``user`` kwarg.
+ * - ``warn_only``
+ - Config: ``run.warn``
+
.. _upgrade-specifics:
@@ -209,6 +325,10 @@
`fabric.connection.Connection` objects and call their methods. These
objects encapsulate all connection state (user, host, gateway, etc) and
have their own SSH client instances.
+
+ .. seealso::
+ `Connection.from_v1 `
+
* - Emphasis on serialized "host strings" as method of setting user, host,
port, etc
- Ported/Removed
@@ -995,6 +1115,11 @@
- This behavior is ultimately unnecessary (one can simply leave the
tilde off for the same result) and had a few pernicious bugs of its
own, so it's gone.
+ * - Naming downloaded files after some aspect of the remote destination, to
+ avoid overwriting during multi-server actions
+ - `Pending https://github.com/fabric/fabric/issues/1868`__
+ - This falls under the `~fabric.group.Group` family, which still needs
+ some work in this regard.
.. _upgrading-configuration:
@@ -1179,7 +1304,7 @@
`fabric.util.get_local_user`.
* - ``env.output_prefix`` determining whether or not line-by-line
host-string prefixes are displayed
- - `Pending https://github.com/pyinvoke/invoke/issues/15`_
+ - `Pending https://github.com/pyinvoke/invoke/issues/15`__
- Differentiating parallel stdout/err is still a work in progress; we may
end up reusing line-by-line logging and prefixing (ideally via actual
logging) or we may try for something cleaner such as streaming to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tasks.py new/fabric-2.4.0/tasks.py
--- old/fabric-2.3.1/tasks.py 2018-07-09 20:09:01.000000000 +0200
+++ new/fabric-2.4.0/tasks.py 2018-09-14 00:29:20.000000000 +0200
@@ -1,5 +1,6 @@
from functools import partial
-from os import environ
+from os import environ, getcwd
+import sys
from invocations import travis
from invocations.checks import blacken
@@ -57,6 +58,45 @@
release.upload(c, directory, index, sign, dry_run)
+@task
+def sanity_test_from_v1(c):
+ """
+ Run some very quick in-process sanity tests on a dual fabric1-v-2 env.
+
+ Assumes Fabric 2+ is already installed as 'fabric2'.
+ """
+ # This cannot, by definition, work under Python 3 as Fabric 1 is not Python
+ # 3 compatible.
+ PYTHON = environ.get("TRAVIS_PYTHON_VERSION", "")
+ if PYTHON.startswith("3") or PYTHON == "pypy3":
+ return
+ c.run("pip install 'fabric<2'")
+ # Make darn sure the two copies of fabric are coming from install root, not
+ # local directory - which would result in 'fabric' always being v2!
+ for serious in (getcwd(), ""):
+ if serious in sys.path: # because why would .remove be idempotent?!
+ sys.path.remove(serious)
+
+ from fabric.api import env
+ from fabric2 import Connection
+
+ env.gateway = "some-gateway"
+ env.no_agent = True
+ env.password = "sikrit"
+ env.user = "admin"
+ env.host_string = "localghost"
+ env.port = "2222"
+ cxn = Connection.from_v1(env)
+ config = cxn.config
+ assert config.run.pty is True
+ assert config.gateway == "some-gateway"
+ assert config.connect_kwargs.password == "sikrit"
+ assert config.sudo.password == "sikrit"
+ assert cxn.host == "localghost"
+ assert cxn.user == "admin"
+ assert cxn.port == 2222
+
+
# Better than nothing, since we haven't solved "pretend I have some other
# task's signature" yet...
publish.__doc__ = release.publish.__doc__
@@ -75,6 +115,7 @@
travis,
watch_docs,
www,
+ sanity_test_from_v1,
)
ns.configure(
{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_multi_recursive.conf new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_multi_recursive.conf
--- old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_multi_recursive.conf 1970-01-01 01:00:00.000000000 +0100
+++ new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_multi_recursive.conf 2018-09-06 21:28:02.000000000 +0200
@@ -0,0 +1,2 @@
+Host *.tld
+ ProxyJump bastion1.tld,bastion2.tld
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_recursive.conf new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_recursive.conf
--- old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_recursive.conf 1970-01-01 01:00:00.000000000 +0100
+++ new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_recursive.conf 2018-09-06 21:28:02.000000000 +0200
@@ -0,0 +1,2 @@
+Host *.tld
+ ProxyJump bastion.tld
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/_util.py new/fabric-2.4.0/tests/_util.py
--- old/fabric-2.3.1/tests/_util.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/_util.py 2018-09-14 00:29:20.000000000 +0200
@@ -3,6 +3,7 @@
import re
import sys
+from invoke.vendor.lexicon import Lexicon
from pytest_relaxed import trap
from fabric import Connection as Connection_, Config as Config_
@@ -69,3 +70,27 @@
# Make sure we're using our tweaked Config if none was given.
kwargs.setdefault("config", Config())
super(Connection, self).__init__(*args, **kwargs)
+
+
+def faux_v1_env():
+ # Close enough to v1 _AttributeDict...
+ # Contains a copy of enough of v1's defaults to prevent us having to do a
+ # lot of extra .get()s...meh
+ return Lexicon(
+ always_use_pty=True,
+ forward_agent=False,
+ gateway=None,
+ host_string="localghost",
+ key_filename=None,
+ no_agent=False,
+ password=None,
+ port=22,
+ ssh_config_path=None,
+ # Used in a handful of sanity tests, so it gets a 'real' value. eh.
+ sudo_password="nope",
+ sudo_prompt=None,
+ timeout=None,
+ use_ssh_config=False,
+ user="localuser",
+ warn_only=False,
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/config.py new/fabric-2.4.0/tests/config.py
--- old/fabric-2.3.1/tests/config.py 2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/tests/config.py 2018-09-14 00:29:20.000000000 +0200
@@ -2,13 +2,14 @@
from os.path import join, expanduser
from paramiko.config import SSHConfig
+from invoke.vendor.lexicon import Lexicon
from fabric import Config
from fabric.util import get_local_user
from mock import patch, call
-from _util import support
+from _util import support, faux_v1_env
class Config_:
@@ -58,6 +59,124 @@
# just tests the underlying data/attribute driving the behavior.
assert Config().prefix == "fabric"
+ class from_v1:
+ def setup(self):
+ self.env = faux_v1_env()
+
+ def _conf(self, **kwargs):
+ self.env.update(kwargs)
+ return Config.from_v1(self.env)
+
+ def must_be_given_explicit_env_arg(self):
+ config = Config.from_v1(
+ env=Lexicon(self.env, sudo_password="sikrit")
+ )
+ assert config.sudo.password == "sikrit"
+
+ class additional_kwargs:
+ def forwards_arbitrary_kwargs_to_init(self):
+ config = Config.from_v1(
+ self.env,
+ # Vanilla Invoke
+ overrides={"some": "value"},
+ # Fabric
+ system_ssh_path="/what/ever",
+ )
+ assert config.some == "value"
+ assert config._system_ssh_path == "/what/ever"
+
+ def subservient_to_runtime_overrides(self):
+ env = self.env
+ env.sudo_password = "from-v1"
+ config = Config.from_v1(
+ env, overrides={"sudo": {"password": "runtime"}}
+ )
+ assert config.sudo.password == "runtime"
+
+ def connect_kwargs_also_merged_with_imported_values(self):
+ self.env["key_filename"] = "whatever"
+ conf = Config.from_v1(
+ self.env, overrides={"connect_kwargs": {"meh": "effort"}}
+ )
+ assert conf.connect_kwargs["key_filename"] == "whatever"
+ assert conf.connect_kwargs["meh"] == "effort"
+
+ class var_mappings:
+ def always_use_pty(self):
+ # Testing both due to v1-didn't-use-None-default issues
+ config = self._conf(always_use_pty=True)
+ assert config.run.pty is True
+ config = self._conf(always_use_pty=False)
+ assert config.run.pty is False
+
+ def forward_agent(self):
+ config = self._conf(forward_agent=True)
+ assert config.forward_agent is True
+
+ def gateway(self):
+ config = self._conf(gateway="bastion.host")
+ assert config.gateway == "bastion.host"
+
+ class key_filename:
+ def base(self):
+ config = self._conf(key_filename="/some/path")
+ assert (
+ config.connect_kwargs["key_filename"] == "/some/path"
+ )
+
+ def is_not_set_if_None(self):
+ config = self._conf(key_filename=None)
+ assert "key_filename" not in config.connect_kwargs
+
+ def no_agent(self):
+ config = self._conf()
+ assert config.connect_kwargs.allow_agent is True
+ config = self._conf(no_agent=True)
+ assert config.connect_kwargs.allow_agent is False
+
+ class password:
+ def set_just_to_connect_kwargs_if_sudo_password_set(self):
+ # NOTE: default faux env has sudo_password set already...
+ config = self._conf(password="screaming-firehawks")
+ passwd = config.connect_kwargs.password
+ assert passwd == "screaming-firehawks"
+
+ def set_to_both_password_fields_if_necessary(self):
+ config = self._conf(password="sikrit", sudo_password=None)
+ assert config.connect_kwargs.password == "sikrit"
+ assert config.sudo.password == "sikrit"
+
+ def ssh_config_path(self):
+ self.env.ssh_config_path = "/where/ever"
+ config = Config.from_v1(self.env, lazy=True)
+ assert config.ssh_config_path == "/where/ever"
+
+ def sudo_password(self):
+ config = self._conf(sudo_password="sikrit")
+ assert config.sudo.password == "sikrit"
+
+ def sudo_prompt(self):
+ config = self._conf(sudo_prompt="password???")
+ assert config.sudo.prompt == "password???"
+
+ def timeout(self):
+ config = self._conf(timeout=15)
+ assert config.timeouts.connect == 15
+
+ def use_ssh_config(self):
+ # Testing both due to v1-didn't-use-None-default issues
+ config = self._conf(use_ssh_config=True)
+ assert config.load_ssh_configs is True
+ config = self._conf(use_ssh_config=False)
+ assert config.load_ssh_configs is False
+
+ def warn_only(self):
+ # Testing both due to v1-didn't-use-None-default issues
+ config = self._conf(warn_only=True)
+ assert config.run.warn is True
+ config = self._conf(warn_only=False)
+ assert config.run.warn is False
+
class ssh_config_loading:
"ssh_config loading"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/connection.py new/fabric-2.4.0/tests/connection.py
--- old/fabric-2.3.1/tests/connection.py 2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/tests/connection.py 2018-09-14 00:29:20.000000000 +0200
@@ -15,14 +15,16 @@
import pytest # for mark
from pytest import skip, param
from pytest_relaxed import raises
+from invoke.vendor.lexicon import Lexicon
from invoke.config import Config as InvokeConfig
from invoke.exceptions import ThreadException
from fabric import Config as Config_
+from fabric.exceptions import InvalidV1Env
from fabric.util import get_local_user
-from _util import support, Connection, Config
+from _util import support, Connection, Config, faux_v1_env
# Remote is woven in as a config default, so must be patched there
@@ -419,6 +421,39 @@
assert middle == Connection("jumpuser2@jumphost2:872")
assert outermost == Connection("jumpuser@jumphost:373")
+ def wildcards_do_not_trigger_recursion(self):
+ # When #1850 is present, this will RecursionError.
+ conf = self._runtime_config(basename="proxyjump_recursive")
+ cxn = Connection("runtime.tld", config=conf)
+ assert cxn.gateway == Connection("bastion.tld")
+ assert cxn.gateway.gateway is None
+
+ def multihop_plus_wildcards_still_no_recursion(self):
+ conf = self._runtime_config(
+ basename="proxyjump_multi_recursive"
+ )
+ cxn = Connection("runtime.tld", config=conf)
+ outer = cxn.gateway
+ inner = cxn.gateway.gateway
+ assert outer == Connection("bastion1.tld")
+ assert inner == Connection("bastion2.tld")
+ assert inner.gateway is None
+
+ def gateway_Connections_get_parent_connection_configs(self):
+ conf = self._runtime_config(
+ basename="proxyjump",
+ overrides={"some_random_option": "a-value"},
+ )
+ cxn = Connection("runtime", config=conf)
+ # Sanity
+ assert cxn.config is conf
+ assert cxn.gateway == self._expected_gw
+ # Real check
+ assert cxn.gateway.config.some_random_option == "a-value"
+ # Prove copy not reference
+ # TODO: would we ever WANT a reference? can't imagine...
+ assert cxn.gateway.config is not conf
+
class connect_timeout:
def wins_over_default(self):
assert self._runtime_cxn().connect_timeout == 15
@@ -478,6 +513,79 @@
cxn = Connection("host", inline_ssh_env=True)
assert cxn.inline_ssh_env is True
+ class from_v1:
+ def setup(self):
+ self.env = faux_v1_env()
+
+ def _cxn(self, **kwargs):
+ self.env.update(kwargs)
+ return Connection.from_v1(self.env)
+
+ def must_be_given_explicit_env_arg(self):
+ cxn = Connection.from_v1(self.env)
+ assert cxn.host == "localghost"
+
+ class obtaining_config:
+ @patch("fabric.connection.Config.from_v1")
+ def defaults_to_calling_Config_from_v1(self, Config_from_v1):
+ Connection.from_v1(self.env)
+ Config_from_v1.assert_called_once_with(self.env)
+
+ @patch("fabric.connection.Config.from_v1")
+ def may_be_given_config_explicitly(self, Config_from_v1):
+ # Arguably a dupe of regular Connection constructor behavior,
+ # but whatever.
+ Connection.from_v1(env=self.env, config=Config())
+ assert not Config_from_v1.called
+
+ class additional_kwargs:
+ # I.e. as opposed to what happens to the 'env' kwarg...
+ def forwards_arbitrary_kwargs_to_init(self):
+ cxn = Connection.from_v1(
+ self.env,
+ connect_kwargs={"foo": "bar"},
+ inline_ssh_env=True,
+ connect_timeout=15,
+ )
+ assert cxn.connect_kwargs["foo"] == "bar"
+ assert cxn.inline_ssh_env is True
+ assert cxn.connect_timeout == 15
+
+ def conflicting_kwargs_win_over_v1_env_values(self):
+ env = Lexicon(self.env)
+ cxn = Connection.from_v1(
+ env, host="not-localghost", port=2222, user="remoteuser"
+ )
+ assert cxn.host == "not-localghost"
+ assert cxn.user == "remoteuser"
+ assert cxn.port == 2222
+
+ class var_mappings:
+ def host_string(self):
+ cxn = self._cxn() # default is 'localghost'
+ assert cxn.host == "localghost"
+
+ @raises(InvalidV1Env)
+ def None_host_string_errors_usefully(self):
+ self._cxn(host_string=None)
+
+ def user(self):
+ cxn = self._cxn(user="space")
+ assert cxn.user == "space"
+
+ class port:
+ def basic(self):
+ cxn = self._cxn(port=2222)
+ assert cxn.port == 2222
+
+ def casted_to_int(self):
+ cxn = self._cxn(port="2222")
+ assert cxn.port == 2222
+
+ def not_supplied_if_given_in_host_string(self):
+ cxn = self._cxn(host_string="localghost:3737", port=2222)
+ assert cxn.port == 3737
+
class string_representation:
"string representations"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/group.py new/fabric-2.4.0/tests/group.py
--- old/fabric-2.3.1/tests/group.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/group.py 2018-09-14 00:29:20.000000000 +0200
@@ -46,6 +46,22 @@
def not_implemented_in_base_class(self):
Group().run()
+ class close_and_contextmanager_behavior:
+ def close_closes_all_member_connections(self):
+ cxns = [Mock(name=x) for x in ("foo", "bar", "biz")]
+ g = Group.from_connections(cxns)
+ g.close()
+ for c in cxns:
+ c.close.assert_called_once_with()
+
+ def contextmanager_behavior_works_like_Connection(self):
+ cxns = [Mock(name=x) for x in ("foo", "bar", "biz")]
+ g = Group.from_connections(cxns)
+ with g as my_g:
+ assert my_g is g
+ for c in cxns:
+ c.close.assert_called_once_with()
+
def _make_serial_tester(cxns, index, args, kwargs):
args = args[:]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fabric-2.3.1/tests/main.py new/fabric-2.4.0/tests/main.py
--- old/fabric-2.3.1/tests/main.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/main.py 2018-09-14 00:28:41.000000000 +0200
@@ -313,6 +313,22 @@
with cd(os.path.join(support, "yml_conf")):
program.run("fab -i cli.key expect-cli-key-filename")
+ class completion:
+ # NOTE: most completion tests are in Invoke too; this is just an
+ # irritating corner case driven by Fabric's 'remainder' functionality.
+ @trap
+ def complete_flag_does_not_trigger_remainder_only_behavior(self):
+ # When bug present, 'fab --complete -- fab' fails to load any
+ # collections because it thinks it's in remainder-only,
+ # work-without-a-collection mode.
+ with cd(support):
+ program.run("fab --complete -- fab", exit=False)
+ # Cherry-picked sanity checks looking for tasks from fixture
+ # fabfile
+ output = sys.stdout.getvalue()
+ for name in ("build", "deploy", "expect-from-env"):
+ assert name in output
+
class main:
"__main__"