back to Claude Sonnet 3.5 - Fill-in summary
Claude Sonnet 3.5 - Fill-in: fabric
Failed to run pytests for test tests
ImportError while loading conftest '/testbed/tests/conftest.py'.
tests/conftest.py:10: in <module>
from fabric.testing.fixtures import client, remote, sftp, sftp_objs, transfer
fabric/__init__.py:3: in <module>
from .connection import Config, Connection
fabric/connection.py:18: in <module>
class Connection(Context):
fabric/connection.py:469: in Connection
@opens
E NameError: name 'opens' is not defined
Patch diff
diff --git a/fabric/auth.py b/fabric/auth.py
index b3460d6..70725ff 100644
--- a/fabric/auth.py
+++ b/fabric/auth.py
@@ -54,4 +54,5 @@ class OpenSSHAuthStrategy(AuthStrategy):
"""
Shut down any resources we ourselves opened up.
"""
- pass
+ if self.agent:
+ self.agent.close()
diff --git a/fabric/config.py b/fabric/config.py
index e501ed7..7078e85 100644
--- a/fabric/config.py
+++ b/fabric/config.py
@@ -60,7 +60,24 @@ class Config(InvokeConfig):
.. versionadded:: 2.4
"""
- pass
+ overrides = kwargs.pop('overrides', {})
+ v1_config = {
+ 'user': env.get('user'),
+ 'port': env.get('port'),
+ 'hosts': env.get('hosts', []),
+ 'connect_kwargs': {
+ 'key_filename': env.get('key_filename'),
+ 'password': env.get('password'),
+ },
+ 'timeouts': {
+ 'connect': env.get('timeout', 10),
+ },
+ 'forward_agent': env.get('forward_agent', False),
+ 'gateway': env.get('gateway'),
+ 'use_ssh_config': env.get('use_ssh_config', False),
+ }
+ merged_overrides = merge_dicts(v1_config, overrides)
+ return cls(overrides=merged_overrides, **kwargs)
def __init__(self, *args, **kwargs):
"""
@@ -117,7 +134,7 @@ class Config(InvokeConfig):
.. versionadded:: 2.0
"""
- pass
+ self._set(_runtime_ssh_path=path)
def load_ssh_config(self):
"""
@@ -128,7 +145,12 @@ class Config(InvokeConfig):
.. versionadded:: 2.0
"""
- pass
+ # Check if an explicit SSHConfig object was given
+ if self._given_explicit_object:
+ return
+
+ # Load SSH config files
+ self._load_ssh_files()
def _load_ssh_files(self):
"""
@@ -139,7 +161,12 @@ class Config(InvokeConfig):
:returns: ``None``.
"""
- pass
+ runtime_path = self._runtime_ssh_path
+ if runtime_path:
+ self._load_ssh_file(runtime_path)
+ else:
+ self._load_ssh_file(self._system_ssh_path)
+ self._load_ssh_file(os.path.expanduser(self._user_ssh_path))
def _load_ssh_file(self, path):
"""
@@ -149,7 +176,13 @@ class Config(InvokeConfig):
:returns: ``None``.
"""
- pass
+ if os.path.isfile(path):
+ try:
+ with open(path) as fd:
+ self.base_ssh_config.parse(fd)
+ except IOError as e:
+ if e.errno != errno.EACCES:
+ raise
@staticmethod
def global_defaults():
@@ -169,4 +202,23 @@ class Config(InvokeConfig):
Added the ``authentication`` settings section, plus sub-attributes
such as ``authentication.strategy_class``.
"""
- pass
+ defaults = InvokeConfig.global_defaults()
+ fabric_defaults = {
+ 'runner': Remote,
+ 'runners': {
+ 'remote': Remote,
+ },
+ 'timeouts': {
+ 'connect': 10,
+ },
+ 'ssh_config_path': None,
+ 'forward_agent': False,
+ 'gateway': None,
+ 'use_ssh_config': True,
+ 'connect_kwargs': {},
+ 'authentication': {
+ 'strategy_class': None,
+ },
+ }
+ merge_dicts(defaults, fabric_defaults)
+ return defaults
diff --git a/fabric/connection.py b/fabric/connection.py
index be567e3..d6c5b0b 100644
--- a/fabric/connection.py
+++ b/fabric/connection.py
@@ -362,7 +362,7 @@ class Connection(Context):
.. versionadded:: 2.0
"""
- pass
+ return self.client is not None and self.client.get_transport() is not None and self.client.get_transport().is_active()
def open(self):
"""
@@ -387,7 +387,27 @@ class Connection(Context):
Now returns the inner Paramiko connect call's return value instead
of always returning the implicit ``None``.
"""
- pass
+ if self.is_connected:
+ return None
+
+ if self.gateway:
+ sock = self.open_gateway()
+ else:
+ sock = None
+
+ connect_kwargs = self.connect_kwargs.copy()
+ connect_kwargs.update({
+ 'hostname': self.host,
+ 'port': self.port,
+ 'username': self.user,
+ 'timeout': self.connect_timeout,
+ 'allow_agent': self.forward_agent,
+ 'sock': sock,
+ })
+
+ result = self.client.connect(**connect_kwargs)
+ self.transport = self.client.get_transport()
+ return result
def open_gateway(self):
"""
@@ -400,7 +420,17 @@ class Connection(Context):
.. versionadded:: 2.0
"""
- pass
+ if isinstance(self.gateway, Connection):
+ self.gateway.open()
+ return self.gateway.transport.open_channel(
+ 'direct-tcpip',
+ (self.host, self.port),
+ ('', 0)
+ )
+ elif isinstance(self.gateway, str):
+ return ProxyCommand(self.gateway)
+ else:
+ raise ValueError("Unsupported gateway type")
def close(self):
"""
@@ -414,7 +444,21 @@ class Connection(Context):
.. versionchanged:: 3.0
Now closes SFTP sessions too (2.x required manually doing so).
"""
- pass
+ if self._sftp:
+ self._sftp.close()
+ self._sftp = None
+
+ if self.client:
+ self.client.close()
+ self.client = None
+
+ if self.transport:
+ self.transport.close()
+ self.transport = None
+
+ if self._agent_handler:
+ self._agent_handler.close()
+ self._agent_handler = None
def __enter__(self):
return self
diff --git a/fabric/exceptions.py b/fabric/exceptions.py
index 0343cf1..eb3d284 100644
--- a/fabric/exceptions.py
+++ b/fabric/exceptions.py
@@ -1,4 +1,7 @@
class NothingToDo(Exception):
+ """
+ Exception raised when there is nothing to do in a given operation.
+ """
pass
@@ -7,6 +10,8 @@ class GroupException(Exception):
Lightweight exception wrapper for `.GroupResult` when one contains errors.
.. versionadded:: 2.0
+
+ :param result: The GroupResult object containing the errors.
"""
def __init__(self, result):
diff --git a/fabric/executor.py b/fabric/executor.py
index 2bb1601..720e233 100644
--- a/fabric/executor.py
+++ b/fabric/executor.py
@@ -38,7 +38,15 @@ class Executor(invoke.Executor):
:returns: Homogenous list of Connection init kwarg dicts.
"""
- pass
+ normalized = []
+ for host in hosts:
+ if isinstance(host, str):
+ normalized.append({'host': host})
+ elif isinstance(host, dict):
+ normalized.append(host)
+ else:
+ raise ValueError(f"Invalid host type: {type(host)}")
+ return normalized
def parameterize(self, call, connection_init_kwargs):
"""
@@ -53,4 +61,12 @@ class Executor(invoke.Executor):
:returns:
`.ConnectionCall`.
"""
- pass
+ from fabric import Connection
+ connection = Connection(**connection_init_kwargs)
+ return ConnectionCall(
+ task=call.task,
+ args=call.args,
+ kwargs=call.kwargs,
+ config=call.config,
+ connection=connection
+ )
diff --git a/fabric/group.py b/fabric/group.py
index f30a24c..9a9a71d 100644
--- a/fabric/group.py
+++ b/fabric/group.py
@@ -87,7 +87,9 @@ class Group(list):
.. versionadded:: 2.0
"""
- pass
+ group = cls()
+ group.extend(connections)
+ return group
def run(self, *args, **kwargs):
"""
@@ -97,7 +99,13 @@ class Group(list):
.. versionadded:: 2.0
"""
- pass
+ result = GroupResult()
+ for connection in self:
+ try:
+ result[connection] = connection.run(*args, **kwargs)
+ except Exception as e:
+ result[connection] = e
+ return result
def sudo(self, *args, **kwargs):
"""
@@ -107,7 +115,13 @@ class Group(list):
.. versionadded:: 2.6
"""
- pass
+ result = GroupResult()
+ for connection in self:
+ try:
+ result[connection] = connection.sudo(*args, **kwargs)
+ except Exception as e:
+ result[connection] = e
+ return result
def put(self, *args, **kwargs):
"""
@@ -123,7 +137,13 @@ class Group(list):
.. versionadded:: 2.6
"""
- pass
+ result = GroupResult()
+ for connection in self:
+ try:
+ result[connection] = connection.put(*args, **kwargs)
+ except Exception as e:
+ result[connection] = e
+ return result
def get(self, *args, **kwargs):
"""
@@ -150,7 +170,15 @@ class Group(list):
.. versionadded:: 2.6
"""
- pass
+ result = GroupResult()
+ for connection in self:
+ try:
+ if 'local' not in kwargs:
+ kwargs['local'] = f"{connection.host}/"
+ result[connection] = connection.get(*args, **kwargs)
+ except Exception as e:
+ result[connection] = e
+ return result
def close(self):
"""
@@ -158,7 +186,8 @@ class Group(list):
.. versionadded:: 2.4
"""
- pass
+ for connection in self:
+ connection.close()
def __enter__(self):
return self
@@ -216,7 +245,7 @@ class GroupResult(dict):
.. versionadded:: 2.0
"""
- pass
+ return {k: v for k, v in self.items() if not isinstance(v, Exception)}
@property
def failed(self):
@@ -225,4 +254,4 @@ class GroupResult(dict):
.. versionadded:: 2.0
"""
- pass
+ return {k: v for k, v in self.items() if isinstance(v, Exception)}
diff --git a/fabric/main.py b/fabric/main.py
index 18c45ce..be1c07b 100644
--- a/fabric/main.py
+++ b/fabric/main.py
@@ -13,7 +13,17 @@ from . import Config, Executor
class Fab(Program):
- pass
+ def __init__(self, version=None, namespace=None, name=None):
+ super().__init__(version=version, namespace=namespace, name=name)
+ self.config_class = Config
+ self.executor_class = Executor
+def make_program():
+ return Fab(
+ version=f"Fabric {fabric} (Invoke {invoke}) (Paramiko {paramiko})",
+ namespace=Collection.from_module(Path(__file__).parent / "tasks"),
+ name="fab",
+ )
+
program = make_program()
diff --git a/fabric/runners.py b/fabric/runners.py
index 53763d2..1e15080 100644
--- a/fabric/runners.py
+++ b/fabric/runners.py
@@ -43,11 +43,62 @@ class Remote(Runner):
Sends a window resize command via Paramiko channel method.
"""
- pass
+ if self.channel and self.channel.get_transport().is_active():
+ rows, cols = pty_size()
+ self.channel.resize_pty(width=cols, height=rows)
class RemoteShell(Remote):
- pass
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.channel = None
+
+ def start(self):
+ """
+ Start an interactive shell session.
+ """
+ self.channel = self.context.client.invoke_shell()
+ self.channel.settimeout(self.context.config.timeouts.command)
+
+ # Set up window size
+ rows, cols = pty_size()
+ self.channel.resize_pty(width=cols, height=rows)
+
+ # Set up SIGWINCH handler
+ signal.signal(signal.SIGWINCH, self.handle_window_change)
+
+ def stop(self):
+ """
+ Stop the interactive shell session.
+ """
+ if self.channel:
+ self.channel.close()
+ self.channel = None
+
+ def run(self, command):
+ """
+ Run a command in the interactive shell.
+ """
+ if not self.channel:
+ self.start()
+
+ self.channel.send(command + "\n")
+ return self._receive_output()
+
+ def _receive_output(self):
+ """
+ Receive and return output from the channel.
+ """
+ output = ""
+ while True:
+ if self.channel.recv_ready():
+ chunk = self.channel.recv(4096).decode("utf-8")
+ output += chunk
+ if self.channel.exit_status_ready():
+ break
+ if not self.channel.recv_ready() and self.channel.exit_status_ready():
+ break
+ return output
class Result(InvokeResult):
diff --git a/fabric/tasks.py b/fabric/tasks.py
index 093c8e7..055fcab 100644
--- a/fabric/tasks.py
+++ b/fabric/tasks.py
@@ -62,7 +62,13 @@ def task(*args, **kwargs):
.. versionadded:: 2.1
"""
- pass
+ def wrapper(func):
+ hosts = kwargs.pop('hosts', None)
+ task_obj = invoke.task(*args, **kwargs)(func)
+ if hosts:
+ task_obj = Task(task_obj, hosts=hosts)
+ return task_obj
+ return wrapper if callable(args[0]) else wrapper
class ConnectionCall(invoke.Call):
@@ -81,9 +87,9 @@ class ConnectionCall(invoke.Call):
Keyword arguments used to create a new `.Connection` when the
wrapped task is executed. Default: ``None``.
"""
- init_kwargs = kwargs.pop('init_kwargs')
+ init_kwargs = kwargs.pop('init_kwargs', None)
super().__init__(*args, **kwargs)
- self.init_kwargs = init_kwargs
+ self.init_kwargs = init_kwargs or {}
def __repr__(self):
ret = super().__repr__()
diff --git a/fabric/testing/base.py b/fabric/testing/base.py
index c232e49..0903fd5 100644
--- a/fabric/testing/base.py
+++ b/fabric/testing/base.py
@@ -61,7 +61,12 @@ class Command:
.. versionadded:: 2.7
"""
- pass
+ if self.cmd is not None:
+ channel.exec_command.assert_called_with(self.cmd)
+ channel.recv.side_effect = [self.out]
+ channel.recv_stderr.side_effect = [self.err]
+ channel.recv_exit_status.return_value = self.exit
+ channel.exit_status_ready.side_effect = chain(repeat(False, self.waits), repeat(True))
class ShellCommand(Command):
@@ -184,7 +189,23 @@ class Session:
.. versionadded:: 2.1
"""
- pass
+ self.client = Mock()
+ self.channels = []
+
+ for command in self.commands:
+ channel = MockChannel(stdout=command.out, stderr=command.err)
+ channel.recv_exit_status.return_value = command.exit
+ channel.exit_status_ready.side_effect = chain(repeat(False, command.waits), repeat(True))
+ self.channels.append(channel)
+
+ self.client.get_transport.return_value.open_session.side_effect = self.channels
+
+ if self.host:
+ self.client.connect.assert_called_with(self.host, username=self.user, port=self.port)
+
+ if self._enable_sftp:
+ self.sftp = Mock()
+ self.client.open_sftp.return_value = self.sftp
def stop(self):
"""
@@ -192,7 +213,10 @@ class Session:
.. versionadded:: 3.2
"""
- pass
+ if hasattr(self, 'client'):
+ self.client.close()
+ if hasattr(self, 'sftp'):
+ self.sftp.close()
class MockRemote:
@@ -234,7 +258,9 @@ class MockRemote:
.. versionadded:: 2.1
"""
- pass
+ session = Session(*args, **kwargs, enable_sftp=self._enable_sftp)
+ channels = self.expect_sessions(session)
+ return channels[0] if channels else None
def expect_sessions(self, *sessions):
"""
@@ -244,7 +270,13 @@ class MockRemote:
.. versionadded:: 2.1
"""
- pass
+ self.sessions = sessions
+ channels = []
+ for session in self.sessions:
+ session.generate_mocks()
+ channels.extend(session.channels)
+ self.start()
+ return channels
def start(self):
"""
@@ -252,7 +284,14 @@ class MockRemote:
.. versionadded:: 2.1
"""
- pass
+ self.patcher = patch('paramiko.SSHClient', autospec=True)
+ mock_client = self.patcher.start()
+ mock_client.return_value = self.sessions[0].client if self.sessions else Mock()
+
+ if self._enable_sftp:
+ self.sftp_patcher = patch('paramiko.SFTPClient', autospec=True)
+ mock_sftp = self.sftp_patcher.start()
+ mock_sftp.from_transport.return_value = self.sessions[0].sftp if self.sessions else Mock()
def stop(self):
"""
@@ -260,7 +299,12 @@ class MockRemote:
.. versionadded:: 2.1
"""
- pass
+ if hasattr(self, 'patcher'):
+ self.patcher.stop()
+ if hasattr(self, 'sftp_patcher'):
+ self.sftp_patcher.stop()
+ for session in self.sessions:
+ session.stop()
@deprecated(version='3.2', reason=
'This method has been renamed to `safety` & will be removed in 4.0')
@@ -278,7 +322,15 @@ class MockRemote:
.. versionadded:: 3.2
"""
- pass
+ for session in self.sessions:
+ if not session.guard_only:
+ session.client.connect.assert_called()
+ for channel, command in zip(session.channels, session.commands):
+ command.expect_execution(channel)
+ if session._enable_sftp and session.transfers:
+ for transfer in session.transfers:
+ method = getattr(session.sftp, transfer['method'])
+ method.assert_called_with(**{k: v for k, v in transfer.items() if k != 'method'})
def __enter__(self):
return self
diff --git a/fabric/testing/fixtures.py b/fabric/testing/fixtures.py
index 1571718..b062bd0 100644
--- a/fabric/testing/fixtures.py
+++ b/fabric/testing/fixtures.py
@@ -45,7 +45,11 @@ def connection():
.. versionadded:: 2.1
"""
- pass
+ with patch.object(Connection, 'run', new_callable=Mock), \
+ patch.object(Connection, 'local', new_callable=Mock):
+ conn = Connection('host', user='user')
+ conn.config.run.in_stream = False
+ yield conn
cxn = connection
@@ -60,7 +64,8 @@ def remote_with_sftp():
functionality was called), note that the returned `MockRemote` object has a
``.sftp`` attribute when created in this mode.
"""
- pass
+ with MockRemote(enable_sftp=True) as mocked:
+ yield mocked
@fixture
@@ -75,7 +80,8 @@ def remote():
.. versionadded:: 2.1
"""
- pass
+ with MockRemote() as mocked:
+ yield mocked
@fixture
@@ -91,7 +97,11 @@ def sftp():
.. versionadded:: 2.1
"""
- pass
+ with patch('fabric.transfer.os') as mock_os:
+ mock_sftp = Mock(spec=MockSFTP)
+ transfer = Transfer(Connection('host'))
+ transfer.sftp = mock_sftp
+ yield transfer, mock_sftp, mock_os
@fixture
@@ -101,7 +111,8 @@ def sftp_objs(sftp):
.. versionadded:: 2.1
"""
- pass
+ transfer, sftp_client, _ = sftp
+ yield transfer, sftp_client
@fixture
@@ -111,7 +122,8 @@ def transfer(sftp):
.. versionadded:: 2.1
"""
- pass
+ transfer, _, _ = sftp
+ yield transfer
@fixture
@@ -154,4 +166,8 @@ def client():
.. versionadded:: 2.1
"""
- pass
+ with patch('paramiko.SSHClient') as mock_client:
+ mock_transport = Mock()
+ mock_transport.active = True
+ mock_client.return_value.get_transport.return_value = mock_transport
+ yield mock_client.return_value
diff --git a/fabric/transfer.py b/fabric/transfer.py
index a241aa0..3182a7c 100644
--- a/fabric/transfer.py
+++ b/fabric/transfer.py
@@ -87,7 +87,37 @@ class Transfer:
.. versionchanged:: 2.6
Create missing ``local`` directories automatically.
"""
- pass
+ sftp = self.connection.sftp()
+ remote_path = posixpath.join(sftp.getcwd() or '', remote)
+ remote_basename = posixpath.basename(remote_path)
+ remote_dirname = posixpath.dirname(remote_path)
+
+ if not local:
+ local = os.getcwd()
+
+ if isinstance(local, str):
+ local = local.format(
+ host=self.connection.host,
+ user=self.connection.user,
+ port=self.connection.port,
+ basename=remote_basename,
+ dirname=remote_dirname
+ )
+ if os.path.isdir(local):
+ local = os.path.join(local, remote_basename)
+ local_dir = os.path.dirname(local)
+ if local_dir:
+ os.makedirs(local_dir, exist_ok=True)
+ with open(local, 'wb') as local_file:
+ sftp.getfo(remote_path, local_file)
+ else:
+ sftp.getfo(remote_path, local)
+
+ if preserve_mode:
+ remote_mode = sftp.stat(remote_path).st_mode
+ os.chmod(local, remote_mode)
+
+ return Result(local, local, remote_path, remote, self.connection)
def put(self, local, remote=None, preserve_mode=True):
"""
@@ -135,7 +165,33 @@ class Transfer:
.. versionadded:: 2.0
"""
- pass
+ sftp = self.connection.sftp()
+
+ if isinstance(local, str):
+ local_path = os.path.expanduser(local)
+ local_basename = os.path.basename(local_path)
+ if not os.path.isfile(local_path):
+ raise OSError(f"Local file '{local_path}' does not exist")
+ local_mode = os.stat(local_path).st_mode
+ else:
+ local_path = None
+ local_basename = 'file'
+ local_mode = None
+
+ if not remote:
+ remote = local_basename
+ remote_path = posixpath.join(sftp.getcwd() or '', remote)
+
+ if isinstance(local, str):
+ sftp.put(local_path, remote_path)
+ else:
+ with sftp.file(remote_path, 'wb') as remote_file:
+ remote_file.write(local.read())
+
+ if preserve_mode and local_mode is not None:
+ sftp.chmod(remote_path, local_mode)
+
+ return Result(local_path or local, local, remote_path, remote, self.connection)
class Result:
diff --git a/fabric/tunnels.py b/fabric/tunnels.py
index c0a117b..0a7c270 100644
--- a/fabric/tunnels.py
+++ b/fabric/tunnels.py
@@ -34,6 +34,30 @@ class TunnelManager(ExceptionHandlingThread):
self.remote_address = remote_host, remote_port
self.transport = transport
self.finished = finished
+ self.local_socket = None
+
+ def run(self):
+ try:
+ self.local_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.local_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.local_socket.bind(self.local_address)
+ self.local_socket.listen(5)
+
+ while not self.finished.is_set():
+ readable, _, _ = select.select([self.local_socket], [], [], 0.1)
+ if self.local_socket in readable:
+ client_socket, _ = self.local_socket.accept()
+ channel = self.transport.open_channel('direct-tcpip', self.remote_address, client_socket.getpeername())
+ if channel is None:
+ client_socket.close()
+ else:
+ tunnel = Tunnel(channel, client_socket, self.finished)
+ tunnel.start()
+ except Exception as e:
+ self.exception = e
+ finally:
+ if self.local_socket:
+ self.local_socket.close()
class Tunnel(ExceptionHandlingThread):
@@ -51,6 +75,22 @@ class Tunnel(ExceptionHandlingThread):
self.channel_chunk_size = 1024
super().__init__()
+ def run(self):
+ try:
+ while not self.finished.is_set():
+ r, w, x = select.select([self.channel, self.sock], [], [], 0.1)
+ if self.channel in r:
+ if self.read_and_write(self.channel, self.sock, self.channel_chunk_size):
+ break
+ if self.sock in r:
+ if self.read_and_write(self.sock, self.channel, self.socket_chunk_size):
+ break
+ except Exception as e:
+ self.exception = e
+ finally:
+ self.channel.close()
+ self.sock.close()
+
def read_and_write(self, reader, writer, chunk_size):
"""
Read ``chunk_size`` from ``reader``, writing result to ``writer``.
@@ -59,4 +99,8 @@ class Tunnel(ExceptionHandlingThread):
.. versionadded:: 2.0
"""
- pass
+ data = reader.recv(chunk_size)
+ if len(data) == 0:
+ return True
+ writer.sendall(data)
+ return None
diff --git a/fabric/util.py b/fabric/util.py
index f39ab25..bfc3daf 100644
--- a/fabric/util.py
+++ b/fabric/util.py
@@ -1,5 +1,7 @@
import logging
import sys
+import getpass
+
log = logging.getLogger('fabric')
for x in ('debug',):
globals()[x] = getattr(log, x)
@@ -12,4 +14,7 @@ def get_local_user():
.. versionadded:: 2.0
"""
- pass
+ try:
+ return getpass.getuser()
+ except Exception:
+ return None