diff --git a/features/help.feature b/features/help.feature index 3462d416f2..ecbef18841 100644 --- a/features/help.feature +++ b/features/help.feature @@ -101,6 +101,16 @@ Feature: Pro Client help text the token on the cli --format \{cli,json\} output in the specified format \(default: cli\) """ + When I run `pro auto-attach --help` as non-root + Then stdout matches regexp: + """ + usage: pro auto-attach \[flags\] + + Automatically attach on an Ubuntu Pro cloud instance. + + (optional arguments|options): + -h, --help show this help message and exit + """ Examples: ubuntu release | release | machine_type | diff --git a/uaclient/cli/__init__.py b/uaclient/cli/__init__.py index e566ce7339..c208499430 100644 --- a/uaclient/cli/__init__.py +++ b/uaclient/cli/__init__.py @@ -28,10 +28,6 @@ util, version, ) -from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import ( - FullAutoAttachOptions, - _full_auto_attach, -) from uaclient.api.u.pro.security.status.reboot_required.v1 import ( _reboot_required, ) @@ -39,6 +35,7 @@ from uaclient.cli import cli_util, fix from uaclient.cli.api import api_command from uaclient.cli.attach import attach_command +from uaclient.cli.auto_attach import auto_attach_command from uaclient.cli.collect_logs import collect_logs_command from uaclient.cli.constants import NAME, USAGE_TMPL from uaclient.cli.disable import disable_command, perform_disable @@ -60,6 +57,7 @@ COMMANDS = [ api_command, attach_command, + auto_attach_command, collect_logs_command, disable_command, enable_command, @@ -84,15 +82,6 @@ def error(self, message): self.exit(2, message + "\n") -def auto_attach_parser(parser): - """Build or extend an arg parser for auto-attach subcommand.""" - parser.prog = "auto-attach" - parser.description = messages.CLI_AUTO_ATTACH_DESC - parser.usage = USAGE_TMPL.format(name=NAME, command=parser.prog) - parser._optionals.title = messages.CLI_FLAGS - return parser - - def config_show_parser(parser, parent_command: str): """Build or extend an arg parser for 'config show' subcommand.""" parser.usage = USAGE_TMPL.format( @@ -679,22 +668,6 @@ def _detach(cfg: config.UAConfig, assume_yes: bool, json_output: bool) -> int: return 0 -@cli_util.assert_root -def action_auto_attach(args, *, cfg: config.UAConfig, **kwargs) -> int: - try: - _full_auto_attach( - FullAutoAttachOptions(), - cfg=cfg, - mode=event_logger.EventLoggerMode.CLI, - ) - except exceptions.ConnectivityError: - event.info(messages.E_ATTACH_FAILURE.msg) - return 1 - else: - cli_util.post_cli_attach(cfg) - return 0 - - def get_parser(cfg: config.UAConfig): parser = UAArgumentParser( prog=NAME, @@ -721,12 +694,6 @@ def get_parser(cfg: config.UAConfig): for command in COMMANDS: command.register(subparsers) - parser_auto_attach = subparsers.add_parser( - "auto-attach", help=messages.CLI_ROOT_AUTO_ATTACH - ) - auto_attach_parser(parser_auto_attach) - parser_auto_attach.set_defaults(action=action_auto_attach) - parser_config = subparsers.add_parser( "config", help=messages.CLI_ROOT_CONFIG ) diff --git a/uaclient/cli/auto_attach.py b/uaclient/cli/auto_attach.py new file mode 100644 index 0000000000..f076f0434a --- /dev/null +++ b/uaclient/cli/auto_attach.py @@ -0,0 +1,33 @@ +from uaclient import event_logger, exceptions, messages +from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import ( + FullAutoAttachOptions, + _full_auto_attach, +) +from uaclient.cli import cli_util +from uaclient.cli.commands import ProCommand + +event = event_logger.get_event_logger() + + +@cli_util.assert_root +def action_auto_attach(args, *, cfg, **kwargs) -> int: + try: + _full_auto_attach( + FullAutoAttachOptions(), + cfg=cfg, + mode=event_logger.EventLoggerMode.CLI, + ) + except exceptions.ConnectivityError: + event.info(messages.E_ATTACH_FAILURE.msg) + return 1 + else: + cli_util.post_cli_attach(cfg) + return 0 + + +auto_attach_command = ProCommand( + "auto-attach", + help=messages.CLI_ROOT_AUTO_ATTACH, + description=messages.CLI_AUTO_ATTACH_DESC, + action=action_auto_attach, +) diff --git a/uaclient/cli/tests/test_cli_auto_attach.py b/uaclient/cli/tests/test_cli_auto_attach.py index 4278445274..fbcf4d3f7f 100644 --- a/uaclient/cli/tests/test_cli_auto_attach.py +++ b/uaclient/cli/tests/test_cli_auto_attach.py @@ -1,5 +1,4 @@ import logging -import textwrap import mock import pytest @@ -9,57 +8,24 @@ from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import ( FullAutoAttachOptions, ) -from uaclient.cli import ( - action_auto_attach, - auto_attach_parser, - get_parser, - main, - main_error_handler, -) +from uaclient.cli import main_error_handler +from uaclient.cli.auto_attach import auto_attach_command from uaclient.testing import fakes -M_PATH = "uaclient.cli." +M_PATH = "uaclient.cli.auto_attach." M_ID_PATH = "uaclient.clouds.identity." -HELP_OUTPUT = textwrap.dedent( - """\ -usage: pro auto-attach [flags] - -Automatically attach on an Ubuntu Pro cloud instance. - -Flags: - -h, --help show this help message and exit -""" -) - -@mock.patch(M_PATH + "util.we_are_currently_root", return_value=False) +@mock.patch(M_PATH + "cli_util.util.we_are_currently_root", return_value=False) def test_non_root_users_are_rejected(we_are_currently_root, FakeConfig): """Check that a UID != 0 will receive a message and exit non-zero""" cfg = FakeConfig() with pytest.raises(exceptions.NonRootUserError): - action_auto_attach(mock.MagicMock(), cfg=cfg) + auto_attach_command.action(mock.MagicMock(), cfg=cfg) class TestActionAutoAttach: - @mock.patch("uaclient.log.setup_cli_logging") - @mock.patch(M_PATH + "contract.get_available_resources") - def test_auto_attach_help( - self, _m_resources, _m_setup_logging, capsys, FakeConfig - ): - with pytest.raises(SystemExit): - with mock.patch( - "sys.argv", ["/usr/bin/ua", "auto-attach", "--help"] - ): - with mock.patch( - "uaclient.config.UAConfig", - return_value=FakeConfig(), - ): - main() - out, _err = capsys.readouterr() - assert HELP_OUTPUT == out - @mock.patch(M_PATH + "cli_util.post_cli_attach") @mock.patch(M_PATH + "_full_auto_attach") def test_happy_path( @@ -70,7 +36,7 @@ def test_happy_path( ): cfg = FakeConfig() - assert 0 == action_auto_attach(mock.MagicMock(), cfg=cfg) + assert 0 == auto_attach_command.action(mock.MagicMock(), cfg=cfg) assert [ mock.call( @@ -109,7 +75,9 @@ def test_handle_full_auto_attach_errors( m_full_auto_attach.side_effect = (api_side_effect,) cfg = FakeConfig() - assert expected_ret == action_auto_attach(mock.MagicMock(), cfg=cfg) + assert expected_ret == auto_attach_command.action( + mock.MagicMock(), cfg=cfg + ) assert [mock.call(expected_err)] == m_event.info.call_args_list assert [] == m_post_cli_attach.call_args_list @@ -160,26 +128,10 @@ def test_uncaught_errors_are_handled( m_full_auto_attach.side_effect = api_side_effect cfg = FakeConfig() with pytest.raises(SystemExit) as excinfo: - main_error_handler(action_auto_attach)(mock.MagicMock(), cfg=cfg) + main_error_handler(auto_attach_command.action)( + mock.MagicMock(), cfg=cfg + ) assert expected_ret == excinfo.value.code _, err = capsys.readouterr() assert expected_err == err assert [] == m_post_cli_attach.call_args_list - - -class TestParser: - @mock.patch(M_PATH + "contract.get_available_resources") - def test_auto_attach_parser_updates_parser_config( - self, _m_resources, FakeConfig - ): - """Update the parser configuration for 'auto-attach'.""" - m_parser = auto_attach_parser(mock.Mock()) - assert "pro auto-attach [flags]" == m_parser.usage - assert "auto-attach" == m_parser.prog - assert "Flags" == m_parser._optionals.title - - full_parser = get_parser(FakeConfig()) - with mock.patch("sys.argv", ["pro", "auto-attach"]): - args = full_parser.parse_args() - assert "auto-attach" == args.command - assert "action_auto_attach" == args.action.__name__