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 <[email protected]> + +- 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 + <fabric.connection.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 <fabric.group.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 <fabric.connection.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 <fabric.connection.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 +<fabric.connection.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 +<fabric.config.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 +<fabric.connection.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 <fabric.connection.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__"
