Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/reference/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Below is a list with all available subcommands.
duplicate Duplicate a computer allowing to change some parameters.
enable Enable the computer for the given user.
export Export the setup or configuration of a computer.
goto Open a shell connecting to the remote computer.
list List all available computers.
relabel Relabel a computer.
setup Create a new computer.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/topics/transport_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def gettree(self, remotepath, localpath, *args, **kwargs):
:param str localpath: local_folder_path
"""

def gotocomputer_command(self, remotedir):
def gotocomputer_command(self, remotedir=None):
"""Return a string to be run using os.system in order to connect via the transport to the remote directory.

Expected behaviors:
Expand Down
24 changes: 24 additions & 0 deletions src/aiida/cmdline/commands/cmd_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
###########################################################################
"""`verdi computer` command."""

import os
import pathlib
import traceback
from copy import deepcopy
Expand Down Expand Up @@ -448,6 +449,29 @@ def computer_list(all_entries, raw):
echo.echo_formatted_list(computers, ['label'], sort=sort, highlight=highlight, hide=hide)


@verdi_computer.command('goto')
@arguments.COMPUTER()
def computer_goto(computer):
"""Open a shell connecting to the remote computer.

This command opens a ssh connection to the remote
computer specified on the command line.
"""
from aiida.common.exceptions import NotExistent

try:
transport = computer.get_transport()
except NotExistent as exception:
echo.echo_critical(repr(exception))

try:
command = transport.gotocomputer_command()
echo.echo_report('going to the remote work directory...')
os.system(command)
except NotImplementedError:
echo.echo_report(f'gotocomputer is not implemented for {transport}')


@verdi_computer.command('show')
@arguments.COMPUTER()
@with_dbenv()
Expand Down
3 changes: 1 addition & 2 deletions src/aiida/transports/plugins/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ def line_encoder(iterator, encoding='utf-8'):

return retval, output_text, stderr_text

def gotocomputer_command(self, remotedir: TransportPath):
def gotocomputer_command(self, remotedir: Optional[TransportPath] = None):
"""Return a string to be run using os.system in order to connect
via the transport to the remote directory.

Expand All @@ -863,7 +863,6 @@ def gotocomputer_command(self, remotedir: TransportPath):

:param str remotedir: the full path of the remote directory
"""
remotedir = str(remotedir)
connect_string = self._gotocomputer_string(remotedir)
cmd = f'bash -c {connect_string}'
return cmd
Expand Down
7 changes: 3 additions & 4 deletions src/aiida/transports/plugins/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
import re
from stat import S_ISDIR, S_ISREG
from typing import Optional

import click

Expand Down Expand Up @@ -1565,12 +1566,10 @@ def exec_command_wait_bytes(

return (retval, b''.join(stdout_bytes), b''.join(stderr_bytes))

def gotocomputer_command(self, remotedir: TransportPath):
def gotocomputer_command(self, remotedir: Optional[TransportPath] = None):
"""Specific gotocomputer string to connect to a given remote computer via
ssh and directly go to the calculation folder.
"""
remotedir = str(remotedir)

further_params = []
if 'username' in self._connect_args:
further_params.append(f"-l {escape_for_bash(self._connect_args['username'])}")
Expand All @@ -1589,7 +1588,7 @@ def gotocomputer_command(self, remotedir: TransportPath):

further_params_str = ' '.join(further_params)

connect_string = self._gotocomputer_string(remotedir)
connect_string = self._gotocomputer_string(remotedir=remotedir)
cmd = f'ssh -t {self._machine} {further_params_str} {connect_string}'
return cmd

Expand Down
4 changes: 2 additions & 2 deletions src/aiida/transports/plugins/ssh_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,13 +1288,13 @@ async def copy_from_remote_to_remote_async(
os.path.join(sandbox.abspath, filename), remotedestination, **kwargs_put
)

def gotocomputer_command(self, remotedir: TransportPath):
def gotocomputer_command(self, remotedir: Optional[TransportPath] = None):
"""Return a string to be used to connect to the remote computer.

:param remotedir: the remote directory to connect to

:type remotedir: :class:`Path <pathlib.Path>`, :class:`PurePosixPath <pathlib.PurePosixPath>`, or `str`
"""
connect_string = self._gotocomputer_string(remotedir)
connect_string = self._gotocomputer_string(remotedir=remotedir)
cmd = f'ssh -t {self.machine} {connect_string}'
return cmd
7 changes: 5 additions & 2 deletions src/aiida/transports/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,11 @@ def get_safe_open_interval(self):
"""
return self._safe_open_interval

def _gotocomputer_string(self, remotedir):
def _gotocomputer_string(self, remotedir=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _gotocomputer_string(self, remotedir=None):
def _gotocomputer_string(self, remotedir: Optional[TransportPath] = None):

"""Command executed when goto computer."""
if remotedir is None:
return self._bash_command_str
remotedir = str(remotedir)
connect_string = (
""" "if [ -d {escaped_remotedir} ] ;"""
""" then cd {escaped_remotedir} ; {bash_command} ; else echo ' ** The directory' ; """
Expand Down Expand Up @@ -820,7 +823,7 @@ def rmtree(self, path: TransportPath):
"""

@abc.abstractmethod
def gotocomputer_command(self, remotedir: TransportPath):
def gotocomputer_command(self, remotedir: Optional[TransportPath] = None):
"""Return a string to be run using os.system in order to connect
via the transport to the remote directory.

Expand Down
37 changes: 37 additions & 0 deletions tests/cmdline/commands/test_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
computer_duplicate,
computer_export_config,
computer_export_setup,
computer_goto,
computer_list,
computer_relabel,
computer_setup,
Expand Down Expand Up @@ -1027,3 +1028,39 @@ def test_computer_setup_with_various_transport(run_cli_command, aiida_computer,
options = [transport_type, computer.uuid] + config
run_cli_command(computer_configure, options, use_subprocess=False)
assert computer.is_configured


def test_computer_goto(run_cli_command, aiida_localhost):
"""Test verdi computer goto command."""

from unittest.mock import patch

from aiida.common.exceptions import NotExistent

options = [str(aiida_localhost.label)]

# Simple case:no exception
with patch('os.system') as mock_os_system:
run_cli_command(computer_goto, options, use_subprocess=False)
mock_os_system.assert_called_once()
assert mock_os_system.call_args[0][0] is not None

def raise_(e):
raise e('something-AAA')

# Test when get_transport raises NotExistent
with patch('aiida.orm.computers.Computer.get_transport', new=lambda _: raise_(NotExistent)):
# The run_cli_command wraps the actual exception into an AssertionError
with pytest.raises(AssertionError) as exc_info:
run_cli_command(computer_goto, options, use_subprocess=False)
assert 'something-AAA' in str(exc_info.value)

# Test when gotocomputer_command raises NotImplementedError
with patch(
'aiida.transports.plugins.local.LocalTransport.gotocomputer_command',
new=lambda _, __=None: raise_(NotImplementedError('something-BBB')),
):
# The run_cli_command wraps the actual exception into an AssertionError
with pytest.raises(AssertionError) as exc_info:
run_cli_command(computer_goto, options, use_subprocess=False)
assert 'something-BBB' in str(exc_info.value)
Loading