From 2d634658509dc488a53f8e903091416d109a2db0 Mon Sep 17 00:00:00 2001 From: Sean Boult <116204283+sboult@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:13:14 -0600 Subject: [PATCH] feat: add console command Attempt to open the default web browser to the AWS managment console (console.aws.amazon.com) --- .gitignore | 1 + awscli/autocomplete/local/indexer.py | 4 +- awscli/customizations/console/__init__.py | 18 +++++++ awscli/customizations/console/open.py | 32 ++++++++++++ awscli/handlers.py | 2 + tests/unit/customizations/console/__init__.py | 3 ++ .../unit/customizations/console/test_open.py | 52 +++++++++++++++++++ 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 awscli/customizations/console/__init__.py create mode 100644 awscli/customizations/console/open.py create mode 100644 tests/unit/customizations/console/__init__.py create mode 100644 tests/unit/customizations/console/test_open.py diff --git a/.gitignore b/.gitignore index 8549cb17e66c..b0cdb02d2f4a 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ doc/source/tutorial/services.rst macpkg/dist/aws macpkg/build dist/ +.venv # autocomplete index awscli/data/ac.index diff --git a/awscli/autocomplete/local/indexer.py b/awscli/autocomplete/local/indexer.py index 0649189f75d0..d939db1411bf 100644 --- a/awscli/autocomplete/local/indexer.py +++ b/awscli/autocomplete/local/indexer.py @@ -25,7 +25,7 @@ class ModelIndexer: 'ddb': 'High level DynamoDB commands', } - _NON_SERVICE_COMMANDS = ['configure', 'history', 'cli-dev', 'login', 'logout'] + _NON_SERVICE_COMMANDS = ['configure', 'history', 'cli-dev', 'login', 'logout', 'console'] _CREATE_CMD_TABLE = """\ CREATE TABLE IF NOT EXISTS command_table ( @@ -111,7 +111,7 @@ def _get_service_full_name(self, name, help_command_table): if name in self._HIGH_LEVEL_SERVICE_FULL_NAMES: return self._HIGH_LEVEL_SERVICE_FULL_NAMES[name] service = help_command_table.get(name) - if service: + if service and hasattr(service, 'service_model'): return service.service_model.metadata['serviceFullName'] def _generate_command_index( diff --git a/awscli/customizations/console/__init__.py b/awscli/customizations/console/__init__.py new file mode 100644 index 000000000000..cfad3999ac87 --- /dev/null +++ b/awscli/customizations/console/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from awscli.customizations.console.open import OpenConsoleCommand + + +def register_console_commands(cli): + cli.register('building-command-table.main', OpenConsoleCommand.add_command) diff --git a/awscli/customizations/console/open.py b/awscli/customizations/console/open.py new file mode 100644 index 000000000000..b256fb23c370 --- /dev/null +++ b/awscli/customizations/console/open.py @@ -0,0 +1,32 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import sys +import webbrowser + +from awscli.customizations.commands import BasicCommand + + +class OpenConsoleCommand(BasicCommand): + NAME = 'console' + DESCRIPTION = ( + 'Open the AWS Management Console in the default browser.' + ) + ARG_TABLE = [ + ] + + def __init__( + self, + session, + token_loader=None, + config_file_writer=None, + ): + super().__init__(session) + + def _run_main(self, parsed_args, parsed_globals): + console_url = 'https://console.aws.amazon.com/' + try: + webbrowser.open_new_tab(console_url) + except webbrowser.Error: + print(f'Failed to open browser. Please visit: {console_url}', file=sys.stderr) + return 1 + return 0 diff --git a/awscli/handlers.py b/awscli/handlers.py index 9c605dc37c33..bdbbabb96c83 100644 --- a/awscli/handlers.py +++ b/awscli/handlers.py @@ -124,6 +124,7 @@ from awscli.customizations.sessendemail import register_ses_send_email from awscli.customizations.sessionmanager import register_ssm_session from awscli.customizations.sso import register_sso_commands +from awscli.customizations.console import register_console_commands from awscli.customizations.streamingoutputarg import add_streaming_output_arg from awscli.customizations.timestampformat import register_timestamp_format from awscli.customizations.toplevelbool import register_bool_params @@ -239,3 +240,4 @@ def awscli_initialize(event_handlers): register_quicksight_asset_bundle_customizations(event_handlers) register_ec2_instance_connect_commands(event_handlers) register_login_cmds(event_handlers) + register_console_commands(event_handlers) diff --git a/tests/unit/customizations/console/__init__.py b/tests/unit/customizations/console/__init__.py new file mode 100644 index 000000000000..1ce4dc983738 --- /dev/null +++ b/tests/unit/customizations/console/__init__.py @@ -0,0 +1,3 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + diff --git a/tests/unit/customizations/console/test_open.py b/tests/unit/customizations/console/test_open.py new file mode 100644 index 000000000000..153fe2405211 --- /dev/null +++ b/tests/unit/customizations/console/test_open.py @@ -0,0 +1,52 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import unittest +import webbrowser +from io import StringIO + +from awscli.testutils import mock + +from awscli.customizations.console.open import OpenConsoleCommand + + +class TestOpenConsoleCommand(unittest.TestCase): + def setUp(self): + self.session = mock.Mock() + self.command = OpenConsoleCommand(self.session) + self.parsed_args = mock.Mock() + self.parsed_globals = mock.Mock() + + @mock.patch('awscli.customizations.console.open.webbrowser.open_new_tab') + def test_opens_console_url_successfully(self, mock_open_browser): + """Test that the command opens the AWS console URL in browser.""" + mock_open_browser.return_value = True + + result = self.command._run_main(self.parsed_args, self.parsed_globals) + + mock_open_browser.assert_called_once_with( + 'https://console.aws.amazon.com/') + self.assertEqual(result, 0) + + @mock.patch('awscli.customizations.console.open.webbrowser.open_new_tab') + @mock.patch('sys.stderr', new_callable=StringIO) + def test_handles_browser_error(self, mock_stderr, mock_open_browser): + """Test that the command handles browser opening errors gracefully.""" + mock_open_browser.side_effect = webbrowser.Error( + 'Browser not available') + + result = self.command._run_main(self.parsed_args, self.parsed_globals) + + mock_open_browser.assert_called_once_with( + 'https://console.aws.amazon.com/') + self.assertEqual(result, 1) + error_output = mock_stderr.getvalue() + self.assertIn('Failed to open browser', error_output) + self.assertIn('https://console.aws.amazon.com/', error_output) + + def test_command_name(self): + """Test that the command has the correct name.""" + self.assertEqual(self.command.NAME, 'console') + + def test_command_description(self): + """Test that the command has a description.""" + self.assertIn('AWS Management Console', self.command.DESCRIPTION)