diff --git a/docs/source/reference/command_line.rst b/docs/source/reference/command_line.rst index 6822df9f0f..4daece5984 100644 --- a/docs/source/reference/command_line.rst +++ b/docs/source/reference/command_line.rst @@ -105,6 +105,7 @@ Below is a list with all available subcommands. disable Disable the computer for the given user. duplicate Duplicate a computer allowing to change some parameters. enable Enable the computer for the given user. + export Exports the Authinfo details for a computer (and user). list List all available computers. relabel Relabel a computer. setup Create a new computer. diff --git a/src/aiida/cmdline/commands/cmd_computer.py b/src/aiida/cmdline/commands/cmd_computer.py index 084f2f6541..baec206144 100644 --- a/src/aiida/cmdline/commands/cmd_computer.py +++ b/src/aiida/cmdline/commands/cmd_computer.py @@ -730,3 +730,62 @@ def computer_config_show(computer, user, defaults, as_option_string): else: table.append((f'* {name}', '-')) echo_tabulate(table, tablefmt='plain') + + +@verdi_computer.group('export', cls=LazyConfigureGroup) +def computer_export(): + """Exports the Authinfo details for a computer (and user).""" + + +@computer_export.command('setup') +@arguments.COMPUTER() +@arguments.OUTPUT_FILE(type=click.Path(exists=False)) +@with_dbenv() +def computer_export_setup(computer, output_file): + """Export computer setup to a yaml file.""" + import yaml + + computer_setup_data = { + 'label': computer.label, + 'description': computer.description, + 'hostname': computer.hostname, + 'transport': computer.transport_type, + 'scheduler': computer.scheduler_type, + 'work_dir': computer.get_workdir(), + 'shebang': computer.get_shebang(), + 'mpirun_command': ' '.join(computer.get_mpirun_command()), + 'mpiprocs_per_machine': computer.get_default_mpiprocs_per_machine(), + 'default_memory_per_machine': computer.get_default_memory_per_machine(), + 'prepend_text': computer.get_prepend_text(), + 'append_text': computer.get_append_text(), + 'use_double_quotes': computer.get_use_double_quotes(), + } + try: + with open(output_file, 'w', encoding='utf-8') as yfhandle: + yaml.dump(computer_setup_data, yfhandle) + echo.echo_success(f"Computer<{computer.pk}> {computer.label} setup exported to file '{output_file}'.") + except Exception: + raise + + +@computer_export.command('config') +@arguments.COMPUTER() +@arguments.OUTPUT_FILE(type=click.Path(exists=False)) +@with_dbenv() +def computer_export_config(computer, output_file): + """Export computer configuration to a yaml file.""" + import yaml + + if not computer.is_configured: + echo.echo_error( + f'Computer<{computer.pk}> {computer.label} config cannot be exported,' + 'because computer has not been configured yet.' + ) + else: + computer_config_data = computer.get_configuration() + try: + with open(output_file, 'w', encoding='utf-8') as yfhandle: + yaml.dump(computer_config_data, yfhandle) + echo.echo_success(f"Computer<{computer.pk}> {computer.label} config exported to file '{output_file}'.") + except Exception: + raise diff --git a/tests/cmdline/commands/test_computer.py b/tests/cmdline/commands/test_computer.py index 0b9bf0a3ad..539261f1d6 100644 --- a/tests/cmdline/commands/test_computer.py +++ b/tests/cmdline/commands/test_computer.py @@ -14,11 +14,14 @@ from collections import OrderedDict import pytest +import yaml from aiida import orm from aiida.cmdline.commands.cmd_computer import ( computer_configure, computer_delete, computer_duplicate, + computer_export_config, + computer_export_setup, computer_list, computer_relabel, computer_setup, @@ -511,6 +514,70 @@ def test_show(self): assert '--username=' in result.output assert result_cur.output == result.output + def test_computer_export_setup(self): + """Test if 'verdi computer export setup' command works""" + self.comp_builder.label = 'test_computer_export_setup' + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() + + exported_setup_filename = 'computer-setup.yml' + try: + result = self.cli_runner(computer_export_setup, [comp.label, exported_setup_filename]) + assert 'Success' in result.output, 'Command should have run successfull.' + assert ( + f'{exported_setup_filename}' in result.output + ), 'Filename should be in terminal output but was not found.' + assert os.path.exists( + exported_setup_filename + ), f"'{exported_setup_filename}' was not created during export." + + # verifying correctness by comparing internal and loaded yml object + with open(exported_setup_filename, 'r', encoding='utf-8') as yfhandle: + configure_setup_data = yaml.safe_load(yfhandle) + assert configure_setup_data == self.comp_builder.get_computer_spec( + comp + ), 'Internal computer configuration does not agree with exported one.' + except Exception: + raise + finally: + if os.path.exists(exported_setup_filename): + os.remove(exported_setup_filename) + + def test_computer_export_config(self): + """Test if 'verdi computer export config' command works""" + self.comp_builder.label = 'test_computer_export_config' + self.comp_builder.transport = 'core.ssh' + comp = self.comp_builder.new() + comp.store() + + exported_config_filename = 'computer-configure.yml' + try: + result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename]) + assert 'Error' in result.output, 'Command should have raised an error.' + + comp.configure(safe_interval=0.0) + result = self.cli_runner(computer_export_config, [comp.label, exported_config_filename]) + assert 'Success' in result.output, 'Command should have run successfull.' + assert ( + f'{exported_config_filename}' in result.output + ), 'Filename should be in terminal output but was not found.' + assert os.path.exists( + exported_config_filename + ), f"'{exported_config_filename}' was not created during export." + + # verifying correctness by comparing internal and loaded yml object + with open(exported_config_filename, 'r', encoding='utf-8') as yfhandle: + configure_config_data = yaml.safe_load(yfhandle) + assert ( + configure_config_data == comp.get_configuration() + ), 'Internal computer configuration does not agree with exported one.' + except Exception: + raise + finally: + if os.path.exists(exported_config_filename): + os.remove(exported_config_filename) + class TestVerdiComputerCommands: """Testing verdi computer commands.