From fb485efd3237aefd83a7e2b3457fc66172e18478 Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 5 Sep 2025 12:54:19 -0400 Subject: [PATCH 01/40] Initial skeleton code and ecr get-login check implementation. --- .../enhancement-Migration-48043.json | 5 +++ awscli/clidriver.py | 23 +++++++++++++ .../customizations/cloudformation/deploy.py | 3 ++ awscli/customizations/scalarparse.py | 1 + awscli/data/cli.json | 5 +++ awscli/migrationcheckers.py | 32 +++++++++++++++++++ 6 files changed, 69 insertions(+) create mode 100644 .changes/next-release/enhancement-Migration-48043.json create mode 100644 awscli/migrationcheckers.py diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json new file mode 100644 index 000000000000..2904d38b63bf --- /dev/null +++ b/.changes/next-release/enhancement-Migration-48043.json @@ -0,0 +1,5 @@ +{ + "type": "enhancement", + "category": "Migration", + "description": "Implement a --migrate-v2 flag that detects breaking changes for AWS CLI v2 for entered commands.\"" +} diff --git a/awscli/clidriver.py b/awscli/clidriver.py index e185fecf0ae4..92bd781d43d2 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -15,6 +15,8 @@ import sys import botocore.session +from awscli.customizations.utils import uni_print +from awscli.migrationcheckers import MIGRATION_CHECKERS from botocore.compat import OrderedDict, copy_kwargs from botocore.exceptions import ( NoCredentialsError, @@ -79,6 +81,7 @@ def main(): def create_clidriver(): session = botocore.session.Session(EnvironmentVariables) _set_user_agent_for_session(session) + # TODO check if full config plugins is empty or not. if it's not, we signal the warning for plugin support being provisional load_plugins( session.full_config.get('plugins', {}), event_hooks=session.get_component('event_emitter'), @@ -220,6 +223,7 @@ def main(self, args=None): parser = self._create_parser(command_table) self._add_aliases(command_table, parser) parsed_args, remaining = parser.parse_known_args(args) + migration_warnings_printed = False try: # Because _handle_top_level_args emits events, it's possible # that exceptions can be raised, which should have the same @@ -231,6 +235,10 @@ def main(self, args=None): 'CLI_VERSION', self.session.user_agent(), 'CLI' ) HISTORY_RECORDER.record('CLI_ARGUMENTS', args, 'CLI') + if parsed_args.migrate_v2: + self._detect_command_migration_breakage(parsed_args, remaining, command_table) + self._print_migration_warnings() + migration_warnings_printed = True return command_table[parsed_args.command](remaining, parsed_args) except UnknownArgumentError as e: sys.stderr.write("usage: %s\n" % USAGE) @@ -259,6 +267,8 @@ def main(self, args=None): except Exception as e: LOG.debug("Exception caught in main()", exc_info=True) LOG.debug("Exiting with rc 255") + if parsed_args.migrate_v2 and not migration_warnings_printed: + self._print_migration_warnings() write_exception(e, outfile=get_stderr_text_writer()) return 255 @@ -310,6 +320,19 @@ def _handle_top_level_args(self, args): logger_name='awscli', log_level=logging.ERROR ) + def _detect_command_migration_breakage(self, parsed_args, remaining, command_table): + if parsed_args.command == 'ecr' and remaining[0] == 'get-login': + MIGRATION_CHECKERS['ecr_get_login'].triggered = True + + + def _print_migration_warnings(self): + migration_warnings = [] + for key, value in MIGRATION_CHECKERS.items(): + if value.triggered: + migration_warnings.append(value.warning_message) + for warning in migration_warnings: + uni_print(f"AWS CLI v2 MIGRATION WARNING: {warning}") + class ServiceCommand(CLICommand): """A service command for the CLI. diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index c4f35c2ec45f..c7572dfb0b47 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -339,6 +339,9 @@ def deploy(self, deployer, stack_name, template_str, tags=tags ) except exceptions.ChangeEmptyError as ex: + # TODO print the runtime check for cli v2 breakage. technically won't be breaking if --fail-on-empty-changeset is + # explicitly provided. but we cannot differentiate between whether fail-on-empty-changeset is true because it's default + # or because it's explicitly specified. if fail_on_empty_changeset: raise write_exception(ex, outfile=get_stdout_text_writer()) diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index 20266f41b91e..a080c9082146 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -64,6 +64,7 @@ def add_timestamp_parser(session): # parser (which parses to a datetime.datetime object) with the # identity function which prints the date exactly the same as it comes # across the wire. + # TODO create an inner function that wraps identity here. it'll signal to print the runtime check. timestamp_parser = identity elif timestamp_format == 'iso8601': timestamp_parser = iso_format diff --git a/awscli/data/cli.json b/awscli/data/cli.json index 85a2efebf537..a0fc23a97e0b 100644 --- a/awscli/data/cli.json +++ b/awscli/data/cli.json @@ -64,6 +64,11 @@ "dest": "connect_timeout", "type": "int", "help": "

The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds.

" + }, + "migrate-v2": { + "action": "store_true", + "dest": "migrate_v2", + "help": "

Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected.

" } } } diff --git a/awscli/migrationcheckers.py b/awscli/migrationcheckers.py new file mode 100644 index 000000000000..3bf6cf9da7b0 --- /dev/null +++ b/awscli/migrationcheckers.py @@ -0,0 +1,32 @@ +# Copyright 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. + +class MigrationCheckerResult: + def __init__(self, triggered, warning_message): + self.triggered = triggered + self.warning_message = warning_message + + +FILE_PREFIX_PARAMS_WARNING = "For input parameters of type blob, behavior when using the file:// prefix changed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-binaryparam." +WEB_LINK_PARAM_WARNING = "For input parameters that have a prefix of http:// or https://, AWS CLI v2 will no longer automatically request the content of the URL for the parameter, and the cli_follow_urlparam option has been removed. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-paramfile." +PAGER_DEFAULT_WARNING = "By default, AWS CLI v2 returns all output through your operating system’s default pager program. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-output-pager." +ECR_GET_LOGIN_WARNING = "The ecr get-login command has been removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-ecr-get-login." +HIDDEN_ALIAS_WARNING = "You have entered a command argument that uses at least 1 of 21 hidden aliases that were removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-aliases." + +MIGRATION_CHECKERS = { + 'file_prefix_param': MigrationCheckerResult(False, FILE_PREFIX_PARAMS_WARNING), + 'web_link_param': MigrationCheckerResult(False, WEB_LINK_PARAM_WARNING), + 'pager_default': MigrationCheckerResult(False, PAGER_DEFAULT_WARNING), + 'ecr_get_login': MigrationCheckerResult(False, ECR_GET_LOGIN_WARNING), + 'hidden_alias': MigrationCheckerResult(False, HIDDEN_ALIAS_WARNING), +} From fb6371866fb5716b78c1149238bef11fbfff9a1a Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 5 Sep 2025 14:43:54 -0400 Subject: [PATCH 02/40] Fix tests. --- awscli/examples/global_options.rst | 5 +++++ awscli/examples/global_synopsis.rst | 1 + tests/unit/test_clidriver.py | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index 2f8c7115e8ae..436a2397fe52 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -69,4 +69,9 @@ ``--cli-connect-timeout`` (int) The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds. + +``--migrate-v2`` (boolean) + + Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected. + diff --git a/awscli/examples/global_synopsis.rst b/awscli/examples/global_synopsis.rst index c5baaa9583ba..d9982ee16117 100644 --- a/awscli/examples/global_synopsis.rst +++ b/awscli/examples/global_synopsis.rst @@ -12,3 +12,4 @@ [--ca-bundle ] [--cli-read-timeout ] [--cli-connect-timeout ] +[--migrate-v2] diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py index b720b0347986..1b5e2dfa68ec 100644 --- a/tests/unit/test_clidriver.py +++ b/tests/unit/test_clidriver.py @@ -91,6 +91,11 @@ "connect-timeout": { "type": "int", "help": "" + }, + "migrate-v2": { + "action": "store_true", + "dest": "migrate_v2", + "help": "", } } }, From 2e8adc85ddeb44d52f3384607eb6756d2ffc30ac Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 5 Sep 2025 15:05:31 -0400 Subject: [PATCH 03/40] Fix tests. --- awscli/examples/global_options.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index 436a2397fe52..2a5f3887c5e9 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -69,9 +69,8 @@ ``--cli-connect-timeout`` (int) The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds. - + ``--migrate-v2`` (boolean) - + Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected. - From 9bc7cd3c613701eaf749b95c56f77fffb699e290 Mon Sep 17 00:00:00 2001 From: Ahmed Moustafa <35640105+aemous@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:12:41 -0400 Subject: [PATCH 04/40] Update .changes/next-release/enhancement-Migration-48043.json Co-authored-by: Alex Shovlin --- .changes/next-release/enhancement-Migration-48043.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json index 2904d38b63bf..562578687c30 100644 --- a/.changes/next-release/enhancement-Migration-48043.json +++ b/.changes/next-release/enhancement-Migration-48043.json @@ -1,5 +1,5 @@ { "type": "enhancement", "category": "Migration", - "description": "Implement a --migrate-v2 flag that detects breaking changes for AWS CLI v2 for entered commands.\"" + "description": "Implement a ``--migrate-v2`` flag that detects breaking changes for AWS CLI v2 for entered commands." } From a5022c8550bbb362ac5d8b4659f99b852b8a3dac Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 17 Sep 2025 16:37:49 -0400 Subject: [PATCH 05/40] Revise based on feedback. --- awscli/clidriver.py | 28 +++---------------------- awscli/customizations/globalargs.py | 20 ++++++++++++++++++ awscli/migrationcheckers.py | 32 ----------------------------- awscli/utils.py | 10 +++++++-- 4 files changed, 31 insertions(+), 59 deletions(-) delete mode 100644 awscli/migrationcheckers.py diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 92bd781d43d2..84078611ee25 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -15,8 +15,6 @@ import sys import botocore.session -from awscli.customizations.utils import uni_print -from awscli.migrationcheckers import MIGRATION_CHECKERS from botocore.compat import OrderedDict, copy_kwargs from botocore.exceptions import ( NoCredentialsError, @@ -223,22 +221,17 @@ def main(self, args=None): parser = self._create_parser(command_table) self._add_aliases(command_table, parser) parsed_args, remaining = parser.parse_known_args(args) - migration_warnings_printed = False try: # Because _handle_top_level_args emits events, it's possible # that exceptions can be raised, which should have the same # general exception handling logic as calling into the # command table. This is why it's in the try/except clause. - self._handle_top_level_args(parsed_args) + self._handle_top_level_args(parsed_args, remaining) self._emit_session_event(parsed_args) HISTORY_RECORDER.record( 'CLI_VERSION', self.session.user_agent(), 'CLI' ) HISTORY_RECORDER.record('CLI_ARGUMENTS', args, 'CLI') - if parsed_args.migrate_v2: - self._detect_command_migration_breakage(parsed_args, remaining, command_table) - self._print_migration_warnings() - migration_warnings_printed = True return command_table[parsed_args.command](remaining, parsed_args) except UnknownArgumentError as e: sys.stderr.write("usage: %s\n" % USAGE) @@ -267,8 +260,6 @@ def main(self, args=None): except Exception as e: LOG.debug("Exception caught in main()", exc_info=True) LOG.debug("Exiting with rc 255") - if parsed_args.migrate_v2 and not migration_warnings_printed: - self._print_migration_warnings() write_exception(e, outfile=get_stderr_text_writer()) return 255 @@ -289,8 +280,8 @@ def _show_error(self, msg): sys.stderr.write(msg) sys.stderr.write('\n') - def _handle_top_level_args(self, args): - emit_top_level_args_parsed_event(self.session, args) + def _handle_top_level_args(self, args, remaining): + emit_top_level_args_parsed_event(self.session, args, remaining, self._get_argument_table()) if args.profile: self.session.set_config_variable('profile', args.profile) if args.region: @@ -320,19 +311,6 @@ def _handle_top_level_args(self, args): logger_name='awscli', log_level=logging.ERROR ) - def _detect_command_migration_breakage(self, parsed_args, remaining, command_table): - if parsed_args.command == 'ecr' and remaining[0] == 'get-login': - MIGRATION_CHECKERS['ecr_get_login'].triggered = True - - - def _print_migration_warnings(self): - migration_warnings = [] - for key, value in MIGRATION_CHECKERS.items(): - if value.triggered: - migration_warnings.append(value.warning_message) - for warning in migration_warnings: - uni_print(f"AWS CLI v2 MIGRATION WARNING: {warning}") - class ServiceCommand(CLICommand): """A service command for the CLI. diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 93321ccd85aa..91e2a9b73058 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -12,6 +12,9 @@ # language governing permissions and limitations under the License. import sys import os + +from awscli.customizations.argrename import HIDDEN_ALIASES +from awscli.customizations.utils import uni_print from botocore.client import Config from botocore import UNSIGNED from botocore.endpoint import DEFAULT_TIMEOUT @@ -30,6 +33,8 @@ def register_parse_global_args(cli): unique_id='resolve-cli-read-timeout') cli.register('top-level-args-parsed', resolve_cli_connect_timeout, unique_id='resolve-cli-connect-timeout') + cli.register('top-level-args-parsed', detect_migration_breakage, + unique_id='detect-migration-breakage') def resolve_types(parsed_args, **kwargs): @@ -90,6 +95,21 @@ def resolve_cli_connect_timeout(parsed_args, session, **kwargs): arg_name = 'connect_timeout' _resolve_timeout(session, parsed_args, arg_name) +def detect_migration_breakage(parsed_args, remaining_args, argument_table, session, **kwargs): + if parsed_args.migrate_v2: + url_params = [param for param in remaining_args if param.startswith('http://') or param.startswith('https://')] + uni_print("AWS CLI v2 MIGRATION WARNING: By default, AWS CLI v2 returns all output through your operating system’s default pager program. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-output-pager.\n") + if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': + uni_print('AWS CLI v2 MIGRATION WARNING: The ecr get-login command has been removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n') + if url_params: + uni_print('AWS CLI v2 MIGRATION WARNING: For input parameters that have a prefix of http:// or https://, AWS CLI v2 will no longer automatically request the content of the URL for the parameter, and the cli_follow_urlparam option has been removed. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-paramfile.\n') + for working, obsolete in HIDDEN_ALIASES.items(): + working_split = working.split('.') + working_service = working_split[0] + working_cmd = working_split[1] + working_param = working_split[2] + if parsed_args.command == working_service and remaining_args[0] == working_cmd and f"--{working_param}" in remaining_args: + uni_print('AWS CLI v2 MIGRATION WARNING: You have entered command arguments that uses at least 1 of 21 hidden aliases that were removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-aliases.\n') def resolve_cli_read_timeout(parsed_args, session, **kwargs): arg_name = 'read_timeout' diff --git a/awscli/migrationcheckers.py b/awscli/migrationcheckers.py deleted file mode 100644 index 3bf6cf9da7b0..000000000000 --- a/awscli/migrationcheckers.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 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. - -class MigrationCheckerResult: - def __init__(self, triggered, warning_message): - self.triggered = triggered - self.warning_message = warning_message - - -FILE_PREFIX_PARAMS_WARNING = "For input parameters of type blob, behavior when using the file:// prefix changed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-binaryparam." -WEB_LINK_PARAM_WARNING = "For input parameters that have a prefix of http:// or https://, AWS CLI v2 will no longer automatically request the content of the URL for the parameter, and the cli_follow_urlparam option has been removed. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-paramfile." -PAGER_DEFAULT_WARNING = "By default, AWS CLI v2 returns all output through your operating system’s default pager program. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-output-pager." -ECR_GET_LOGIN_WARNING = "The ecr get-login command has been removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-ecr-get-login." -HIDDEN_ALIAS_WARNING = "You have entered a command argument that uses at least 1 of 21 hidden aliases that were removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-aliases." - -MIGRATION_CHECKERS = { - 'file_prefix_param': MigrationCheckerResult(False, FILE_PREFIX_PARAMS_WARNING), - 'web_link_param': MigrationCheckerResult(False, WEB_LINK_PARAM_WARNING), - 'pager_default': MigrationCheckerResult(False, PAGER_DEFAULT_WARNING), - 'ecr_get_login': MigrationCheckerResult(False, ECR_GET_LOGIN_WARNING), - 'hidden_alias': MigrationCheckerResult(False, HIDDEN_ALIAS_WARNING), -} diff --git a/awscli/utils.py b/awscli/utils.py index 4a3096320fad..024215b134a1 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -205,8 +205,14 @@ def ignore_ctrl_c(): signal.signal(signal.SIGINT, original) -def emit_top_level_args_parsed_event(session, args): - session.emit('top-level-args-parsed', parsed_args=args, session=session) +def emit_top_level_args_parsed_event(session, args, remaining, argument_table): + session.emit( + 'top-level-args-parsed', + parsed_args=args, + remaining_args=remaining, + argument_table=argument_table, + session=session + ) def is_a_tty(): From 75bc2ec1874375ff7f6f88efe6272a18f0e9fadf Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 24 Sep 2025 14:01:50 -0400 Subject: [PATCH 06/40] Add TODO for api_versions check. --- awscli/clidriver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 84078611ee25..8be565f1c97d 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -80,6 +80,7 @@ def create_clidriver(): session = botocore.session.Session(EnvironmentVariables) _set_user_agent_for_session(session) # TODO check if full config plugins is empty or not. if it's not, we signal the warning for plugin support being provisional + # similarly, we check for api_versions config value here. load_plugins( session.full_config.get('plugins', {}), event_hooks=session.get_component('event_emitter'), From 8c1be9f3dc2a0117db062668e87a35223dc6249d Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 26 Sep 2025 12:04:14 -0400 Subject: [PATCH 07/40] Revised based on feedback. --- awscli/alias.py | 6 +++--- awscli/clidriver.py | 2 +- awscli/customizations/globalargs.py | 7 +++---- awscli/data/cli.json | 4 ++-- awscli/examples/global_options.rst | 2 +- awscli/examples/global_synopsis.rst | 2 +- awscli/utils.py | 3 +-- tests/unit/test_alias.py | 2 +- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/awscli/alias.py b/awscli/alias.py index 29014051ff2e..e09a09c29c68 100644 --- a/awscli/alias.py +++ b/awscli/alias.py @@ -183,7 +183,7 @@ def __call__(self, args, parsed_globals): parsed_alias_args, remaining = self._parser.parse_known_args( alias_args ) - self._update_parsed_globals(parsed_alias_args, parsed_globals) + self._update_parsed_globals(parsed_alias_args, parsed_globals, remaining) # Take any of the remaining arguments that were not parsed out and # prepend them to the remaining args provided to the alias. remaining.extend(args) @@ -228,7 +228,7 @@ def _get_alias_args(self): ) return alias_args - def _update_parsed_globals(self, parsed_alias_args, parsed_globals): + def _update_parsed_globals(self, parsed_alias_args, parsed_globals, remaining): global_params_to_update = self._get_global_parameters_to_update( parsed_alias_args ) @@ -237,7 +237,7 @@ def _update_parsed_globals(self, parsed_alias_args, parsed_globals): # global parameters provided in the alias before updating # the original provided global parameter values # and passing those onto subsequent commands. - emit_top_level_args_parsed_event(self._session, parsed_alias_args) + emit_top_level_args_parsed_event(self._session, parsed_alias_args, remaining) for param_name in global_params_to_update: updated_param_value = getattr(parsed_alias_args, param_name) setattr(parsed_globals, param_name, updated_param_value) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 8be565f1c97d..1945dcbb9141 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -282,7 +282,7 @@ def _show_error(self, msg): sys.stderr.write('\n') def _handle_top_level_args(self, args, remaining): - emit_top_level_args_parsed_event(self.session, args, remaining, self._get_argument_table()) + emit_top_level_args_parsed_event(self.session, args, remaining) if args.profile: self.session.set_config_variable('profile', args.profile) if args.region: diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 91e2a9b73058..2ef6b9ef6a15 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -95,13 +95,12 @@ def resolve_cli_connect_timeout(parsed_args, session, **kwargs): arg_name = 'connect_timeout' _resolve_timeout(session, parsed_args, arg_name) -def detect_migration_breakage(parsed_args, remaining_args, argument_table, session, **kwargs): - if parsed_args.migrate_v2: +def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): + if parsed_args.v2_debug: url_params = [param for param in remaining_args if param.startswith('http://') or param.startswith('https://')] - uni_print("AWS CLI v2 MIGRATION WARNING: By default, AWS CLI v2 returns all output through your operating system’s default pager program. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-output-pager.\n") if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': uni_print('AWS CLI v2 MIGRATION WARNING: The ecr get-login command has been removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n') - if url_params: + if url_params and session.full_config.get('cli_follow_urlparam', True): uni_print('AWS CLI v2 MIGRATION WARNING: For input parameters that have a prefix of http:// or https://, AWS CLI v2 will no longer automatically request the content of the URL for the parameter, and the cli_follow_urlparam option has been removed. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-paramfile.\n') for working, obsolete in HIDDEN_ALIASES.items(): working_split = working.split('.') diff --git a/awscli/data/cli.json b/awscli/data/cli.json index a0fc23a97e0b..25687399d05c 100644 --- a/awscli/data/cli.json +++ b/awscli/data/cli.json @@ -65,9 +65,9 @@ "type": "int", "help": "

The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds.

" }, - "migrate-v2": { + "v2-debug": { "action": "store_true", - "dest": "migrate_v2", + "dest": "v2_debug", "help": "

Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected.

" } } diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index 2a5f3887c5e9..d450cf0ce78a 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -70,7 +70,7 @@ The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds. -``--migrate-v2`` (boolean) +``--v2-debug`` (boolean) Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected. diff --git a/awscli/examples/global_synopsis.rst b/awscli/examples/global_synopsis.rst index d9982ee16117..12865958a809 100644 --- a/awscli/examples/global_synopsis.rst +++ b/awscli/examples/global_synopsis.rst @@ -12,4 +12,4 @@ [--ca-bundle ] [--cli-read-timeout ] [--cli-connect-timeout ] -[--migrate-v2] +[--v2-debug] diff --git a/awscli/utils.py b/awscli/utils.py index 024215b134a1..3e7e9d6e8e37 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -205,12 +205,11 @@ def ignore_ctrl_c(): signal.signal(signal.SIGINT, original) -def emit_top_level_args_parsed_event(session, args, remaining, argument_table): +def emit_top_level_args_parsed_event(session, args, remaining): session.emit( 'top-level-args-parsed', parsed_args=args, remaining_args=remaining, - argument_table=argument_table, session=session ) diff --git a/tests/unit/test_alias.py b/tests/unit/test_alias.py index 853aa6cd903f..7707cf319c93 100644 --- a/tests/unit/test_alias.py +++ b/tests/unit/test_alias.py @@ -396,7 +396,7 @@ def replace_global_param_value_with_foo(event_name, **kwargs): alias_cmd([], FakeParsedArgs(command=self.alias_name)) self.session.emit.assert_called_with( 'top-level-args-parsed', parsed_args=mock.ANY, - session=self.session) + session=self.session, remaining_args=mock.ANY) command_table['myservice'].assert_called_with( ['myoperation'], From 911eddb0f6576135e395c36ddec8c19348abfd5d Mon Sep 17 00:00:00 2001 From: Ahmed Moustafa <35640105+aemous@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:07:57 -0400 Subject: [PATCH 08/40] Implement v2-debug checks for args, config, customizations, s3, and environment variables (#9775) --- awscli/clidriver.py | 48 ++++++- awscli/customizations/cliinputjson.py | 4 + .../customizations/cloudformation/deploy.py | 21 ++- awscli/customizations/globalargs.py | 136 ++++++++++++++++-- awscli/customizations/paginate.py | 62 ++++++++ awscli/customizations/s3/subcommands.py | 19 +++ awscli/customizations/scalarparse.py | 32 ++++- tests/functional/test_api_versions.py | 18 ++- .../cloudformation/test_deploy.py | 6 +- .../customizations/s3/test_subcommands.py | 43 +++++- tests/unit/customizations/test_globalargs.py | 62 +++++++- tests/unit/customizations/test_paginate.py | 90 +++++++++++- tests/unit/customizations/test_scalarparse.py | 10 +- tests/unit/test_clidriver.py | 6 +- 14 files changed, 514 insertions(+), 43 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 1945dcbb9141..1a798aa2c5c0 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -41,6 +41,7 @@ ) from awscli.commands import CLICommand from awscli.compat import get_stderr_text_writer +from awscli.customizations.utils import uni_print from awscli.formatter import get_formatter from awscli.help import ( OperationHelpCommand, @@ -79,8 +80,6 @@ def main(): def create_clidriver(): session = botocore.session.Session(EnvironmentVariables) _set_user_agent_for_session(session) - # TODO check if full config plugins is empty or not. if it's not, we signal the warning for plugin support being provisional - # similarly, we check for api_versions config value here. load_plugins( session.full_config.get('plugins', {}), event_hooks=session.get_component('event_emitter'), @@ -547,6 +546,12 @@ def __call__(self, args, parsed_globals): parsed_args, self.arg_table ) + self._detect_binary_file_migration_change( + self._session, + parsed_args, + parsed_globals, + self.arg_table + ) event = f'calling-command.{self._parent_name}.{self._name}' override = self._emit_first_non_none_response( event, @@ -663,6 +668,45 @@ def _create_operation_parser(self, arg_table): parser = ArgTableArgParser(arg_table) return parser + def _detect_binary_file_migration_change( + self, + session, + parsed_args, + parsed_globals, + arg_table + ): + if ( + session.get_scoped_config() + .get('cli_binary_format', None) == 'raw-in-base64-out' + ): + # if cli_binary_format is set to raw-in-base64-out, then v2 behavior will + # be the same as v1, so there is no breaking change in this case. + return + if parsed_globals.v2_debug: + parsed_args_to_check = { + arg: getattr(parsed_args, arg) + for arg in vars(parsed_args) if getattr(parsed_args, arg) + } + + arg_values_to_check = [ + arg.py_name for arg in arg_table.values() + if arg.py_name in parsed_args_to_check + and arg.argument_model.type_name == 'blob' + and parsed_args_to_check[arg.py_name].startswith('file://') + ] + if arg_values_to_check: + uni_print( + 'AWS CLI v2 MIGRATION WARNING: When specifying a blob-type ' + 'parameter starting with `file://`, AWS CLI v2 will assume ' + 'the content of the file is already base64-encoded. To ' + 'maintain v1 behavior after upgrading to v2, set the ' + '`cli_binary_format` configuration variable to ' + '`raw-in-base64-out`. See https://docs.aws.amazon.com/cli/' + 'latest/userguide/cliv2-migration-changes.html#' + 'cliv2-migration-binaryparam.\n', + out_file=sys.stderr + ) + class CLIOperationCaller: """Call an AWS operation and format the response.""" diff --git a/awscli/customizations/cliinputjson.py b/awscli/customizations/cliinputjson.py index 01864e750f1a..47d58c0c3569 100644 --- a/awscli/customizations/cliinputjson.py +++ b/awscli/customizations/cliinputjson.py @@ -70,6 +70,10 @@ def add_to_call_parameters(self, call_parameters, parsed_args, try: # Try to load the JSON string into a python dictionary input_data = json.loads(retrieved_json) + self._session.register( + f"get-cli-input-json-data", + lambda **inner_kwargs: input_data + ) except ValueError as e: raise ParamError( self.name, "Invalid JSON: %s\nJSON received: %s" diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index c7572dfb0b47..a734453e2e0f 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -24,6 +24,7 @@ from awscli.customizations.commands import BasicCommand from awscli.compat import get_stdout_text_writer +from awscli.customizations.utils import uni_print from awscli.utils import create_nested_client, write_exception LOG = logging.getLogger(__name__) @@ -321,12 +322,13 @@ def _run_main(self, parsed_args, parsed_globals): parsed_args.execute_changeset, parsed_args.role_arn, parsed_args.notification_arns, s3_uploader, tags, parsed_args.fail_on_empty_changeset, - parsed_args.disable_rollback) + parsed_args.disable_rollback, getattr(parsed_globals, 'v2_debug', False)) def deploy(self, deployer, stack_name, template_str, parameters, capabilities, execute_changeset, role_arn, notification_arns, s3_uploader, tags, - fail_on_empty_changeset=True, disable_rollback=False): + fail_on_empty_changeset=True, disable_rollback=False, + v2_debug=False): try: result = deployer.create_and_wait_for_changeset( stack_name=stack_name, @@ -339,10 +341,19 @@ def deploy(self, deployer, stack_name, template_str, tags=tags ) except exceptions.ChangeEmptyError as ex: - # TODO print the runtime check for cli v2 breakage. technically won't be breaking if --fail-on-empty-changeset is - # explicitly provided. but we cannot differentiate between whether fail-on-empty-changeset is true because it's default - # or because it's explicitly specified. if fail_on_empty_changeset: + if v2_debug: + uni_print( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, ' + 'deploying an AWS CloudFormation Template that ' + 'results in an empty changeset will NOT result in an ' + 'error. You can add the -–no-fail-on-empty-changeset ' + 'flag to migrate to v2 behavior and resolve this ' + 'warning. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-cfn.\n', + out_file=sys.stderr + ) raise write_exception(ex, outfile=get_stdout_text_writer()) return 0 diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 2ef6b9ef6a15..96b599ce37ea 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -96,19 +96,129 @@ def resolve_cli_connect_timeout(parsed_args, session, **kwargs): _resolve_timeout(session, parsed_args, arg_name) def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): - if parsed_args.v2_debug: - url_params = [param for param in remaining_args if param.startswith('http://') or param.startswith('https://')] - if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': - uni_print('AWS CLI v2 MIGRATION WARNING: The ecr get-login command has been removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n') - if url_params and session.full_config.get('cli_follow_urlparam', True): - uni_print('AWS CLI v2 MIGRATION WARNING: For input parameters that have a prefix of http:// or https://, AWS CLI v2 will no longer automatically request the content of the URL for the parameter, and the cli_follow_urlparam option has been removed. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-paramfile.\n') - for working, obsolete in HIDDEN_ALIASES.items(): - working_split = working.split('.') - working_service = working_split[0] - working_cmd = working_split[1] - working_param = working_split[2] - if parsed_args.command == working_service and remaining_args[0] == working_cmd and f"--{working_param}" in remaining_args: - uni_print('AWS CLI v2 MIGRATION WARNING: You have entered command arguments that uses at least 1 of 21 hidden aliases that were removed in AWS CLI v2. See https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration-changes.html#cliv2-migration-aliases.\n') + if not parsed_args.v2_debug: + return + url_params = [ + param for param in remaining_args + if param.startswith('http://') or param.startswith('https://') + ] + region = parsed_args.region or session.get_config_variable('region') + s3_config = session.get_config_variable('s3') + if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ: + if 'AWS_CLI_FILE_ENCODING' not in os.environ: + uni_print( + 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'PYTHONIOENCODING environment variables are unsupported ' + 'in AWS CLI v2. AWS CLI v2 uses AWS_CLI_FILE_ENCODING ' + 'instead, set this environment variable to resolve this. ' + 'See https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' + '#cliv2-migration-encodingenvvar.\n', + out_file=sys.stderr + ) + if ( + s3_config is not None and s3_config + .get( + 'us_east_1_regional_endpoint', + 'legacy' + ) == 'legacy' and region in ('us-east-1', None) + ): + session.register( + 'request-created.s3.*', + warn_if_east_configured_global_endpoint + ) + if session.get_config_variable('api_versions'): + uni_print( + 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 does not support ' + 'calling earlier versions of AWS service APIs via the ' + '`api_versions` configuration file setting. To migrate to v2 ' + 'behavior and resolve this warning, remove the `api_versions` ' + 'setting in the configuration file. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-api-versions.\n', + out_file = sys.stderr + ) + if session.full_config.get('plugins', {}): + uni_print( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, plugin support ' + 'is provisional. If you rely on plugins, be sure to lock into ' + 'a particular version of the AWS CLI and test the ' + 'functionality of your plugins for each upgrade. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#' + 'cliv2-migration-profile-plugins\n', + out_file=sys.stderr + ) + if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': + uni_print( + 'AWS CLI v2 MIGRATION WARNING: The ecr get-login command has ' + 'been removed in AWS CLI v2. See https://docs.aws.amazon.com/' + 'cli/latest/userguide/cliv2-migration-changes.html' + '#cliv2-migration-ecr-get-login.\n', + out_file=sys.stderr + ) + if url_params and session.full_config.get('cli_follow_urlparam', True): + uni_print( + 'AWS CLI v2 MIGRATION WARNING: For input parameters that have ' + 'a prefix of http:// or https://, AWS CLI v2 will no longer ' + 'automatically request the content of the URL for the ' + 'parameter, and the cli_follow_urlparam option has been ' + 'removed. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-paramfile.\n', + out_file=sys.stderr + ) + for working, obsolete in HIDDEN_ALIASES.items(): + working_split = working.split('.') + working_service = working_split[0] + working_cmd = working_split[1] + working_param = working_split[2] + if ( + parsed_args.command == working_service + and remaining_args[0] == working_cmd + and f"--{working_param}" in remaining_args + ): + uni_print( + 'AWS CLI v2 MIGRATION WARNING: You have entered command ' + 'arguments that uses at least 1 of 21 hidden aliases that ' + 'were removed in AWS CLI v2. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide' + '/cliv2-migration-changes.html#cliv2-migration-aliases.\n', + out_file=sys.stderr + ) + session.register('choose-signer.s3.*', warn_if_sigv2) + +def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): + # The regional us-east-1 endpoint is used in certain cases (e.g. + # FIPS/Dual-Stack is enabled). Rather than duplicating this logic + # from botocore, we check the endpoint URL directly. + if 's3.amazonaws.com' in request.url: + uni_print( + 'AWS CLI v2 MIGRATION WARNING: When you configure AWS CLI v2 ' + 'to use the `us-east-1` region, it uses the true regional ' + 'endpoint rather than the global endpoint. To continue using ' + 'the global endpoint on v2, configure the region to be ' + '`aws-global`. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-s3-regional-endpoint.\n', + out_file=sys.stderr + ) + +def warn_if_sigv2( + signing_name, + region_name, + signature_version, + context, + **kwargs +): + if context.get('auth_type', None) == 'v2': + uni_print( + 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 only uses Signature ' + 'v4 to authenticate Amazon S3 requests. Run the command `aws ' + 'configure set s3.signature_version s3v4` to migrate to v4 and ' + 'resolve this.\n', + out_file=sys.stderr + ) def resolve_cli_read_timeout(parsed_args, session, **kwargs): arg_name = 'read_timeout' diff --git a/awscli/customizations/paginate.py b/awscli/customizations/paginate.py index fe1f3f140112..836b338b0fef 100644 --- a/awscli/customizations/paginate.py +++ b/awscli/customizations/paginate.py @@ -135,6 +135,9 @@ def unify_paging_params(argument_table, operation_model, event_name, _remove_existing_paging_arguments(argument_table, paginator_config) parsed_args_event = event_name.replace('building-argument-table.', 'operation-args-parsed.') + call_parameters_event = event_name.replace( + 'building-argument-table', 'calling-command' + ) shadowed_args = {} add_paging_argument(argument_table, 'starting-token', PageArgument('starting-token', STARTING_TOKEN_HELP, @@ -168,6 +171,14 @@ def unify_paging_params(argument_table, operation_model, event_name, partial(check_should_enable_pagination, list(_get_all_cli_input_tokens(paginator_config)), shadowed_args, argument_table)) + session.register( + call_parameters_event, + partial( + check_should_enable_pagination_call_parameters, + session, + list(_get_all_input_tokens(paginator_config)), + ), + ) def add_paging_argument(argument_table, arg_name, argument, shadowed_args): @@ -240,6 +251,18 @@ def _get_all_cli_input_tokens(pagination_config): yield cli_name +# Get all tokens but return them in API namespace rather than CLI namespace +def _get_all_input_tokens(pagination_config): + # Get all input tokens including the limit_key + # if it exists. + tokens = _get_input_tokens(pagination_config) + for token_name in tokens: + yield token_name + if 'limit_key' in pagination_config: + key_name = pagination_config['limit_key'] + yield key_name + + def _get_input_tokens(pagination_config): tokens = pagination_config['input_token'] if not isinstance(tokens, list): @@ -253,6 +276,45 @@ def _get_cli_name(param_objects, token_name): return param.cli_name.lstrip('-') +def check_should_enable_pagination_call_parameters( + session, + input_tokens, + call_parameters, + parsed_args, + parsed_globals, + **kwargs +): + """ + Check for pagination args in the actual calling arguments passed to + the function. + + If the user is using the --cli-input-json parameter to provide JSON + parameters they are all in the API naming space rather than the CLI + naming space and would be missed by the processing above. This function + gets called on the calling-command event. + """ + if parsed_globals.v2_debug: + cli_input_json_data = session.emit_first_non_none_response( + f"get-cli-input-json-data", + ) + if cli_input_json_data is None: + cli_input_json_data = {} + pagination_params_in_input_tokens = [ + param for param in cli_input_json_data if param in input_tokens + ] + if pagination_params_in_input_tokens: + uni_print( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'pagination parameters by using a file with the ' + '`--cli-input-json` parameter, automatic pagination will be ' + 'turned off. This is not the case in v1. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' + '#cliv2-migration-skeleton-paging.\n', + out_file=sys.stderr + ) + + class PageArgument(BaseCLIArgument): type_map = { 'string': str, diff --git a/awscli/customizations/s3/subcommands.py b/awscli/customizations/s3/subcommands.py index 3f3a2834c6d5..665fa9ae2603 100644 --- a/awscli/customizations/s3/subcommands.py +++ b/awscli/customizations/s3/subcommands.py @@ -767,6 +767,7 @@ def _run_main(self, parsed_args, parsed_globals): cmd_params.add_verify_ssl(parsed_globals) cmd_params.add_page_size(parsed_args) cmd_params.add_paths(parsed_args.paths) + cmd_params.add_v2_debug(parsed_globals) runtime_config = transferconfig.RuntimeConfig().build_config( **self._session.get_scoped_config().get('s3', {})) @@ -1056,6 +1057,21 @@ def run(self): result_queue = queue.Queue() operation_name = cmd_translation[paths_type] + if self.parameters['v2_debug']: + if operation_name == 'copy': + uni_print( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, object ' + 'properties will be copied from the source in multipart ' + 'copies between S3 buckets. This may result in extra S3 ' + 'API calls being made. Breakage may occur if the principal ' + 'does not have permission to call these extra APIs. This ' + 'warning cannot be resolved. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' + '#cliv2-migration-s3-copy-metadata\n\n', + out_file=sys.stderr + ) + fgen_kwargs = { 'client': self._source_client, 'operation_name': operation_name, 'follow_symlinks': self.parameters['follow_symlinks'], @@ -1447,6 +1463,9 @@ def add_verify_ssl(self, parsed_globals): def add_page_size(self, parsed_args): self.parameters['page_size'] = getattr(parsed_args, 'page_size', None) + def add_v2_debug(self, parsed_globals): + self.parameters['v2_debug'] = parsed_globals.v2_debug + def _validate_sse_c_args(self): self._validate_sse_c_arg() self._validate_sse_c_arg('sse_c_copy_source') diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index a080c9082146..7589897fe665 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -27,9 +27,13 @@ in the future. """ +import sys + from botocore.utils import parse_timestamp from botocore.exceptions import ProfileNotFound +from awscli.customizations.utils import uni_print + def register_scalar_parser(event_handlers): event_handlers.register_first( @@ -44,7 +48,7 @@ def iso_format(value): return parse_timestamp(value).isoformat() -def add_timestamp_parser(session): +def add_timestamp_parser(session, v2_debug): factory = session.get_component('response_parser_factory') try: timestamp_format = session.get_scoped_config().get( @@ -64,8 +68,26 @@ def add_timestamp_parser(session): # parser (which parses to a datetime.datetime object) with the # identity function which prints the date exactly the same as it comes # across the wire. - # TODO create an inner function that wraps identity here. it'll signal to print the runtime check. - timestamp_parser = identity + encountered_timestamp = False + def identity_with_warning(x): + # To prevent printing the same warning for each timestamp in the + # response, we utilize a reference to a nonlocal variable to track + # if we have already printed the warning. + nonlocal encountered_timestamp + if not encountered_timestamp: + encountered_timestamp = True + uni_print( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, all timestamp ' + 'response values are returned in the ISO 8601 format. To ' + 'migrate to v2 behavior and resolve this warning, set the ' + 'configuration variable `cli_timestamp_format` to `iso8601`. ' + 'See https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-timestamp.\n', + out_file=sys.stderr + ) + return identity(x) + + timestamp_parser = identity_with_warning if v2_debug else identity elif timestamp_format == 'iso8601': timestamp_parser = iso_format else: @@ -74,7 +96,7 @@ def add_timestamp_parser(session): factory.set_parser_defaults(timestamp_parser=timestamp_parser) -def add_scalar_parsers(session, **kwargs): +def add_scalar_parsers(session, parsed_args, **kwargs): factory = session.get_component('response_parser_factory') factory.set_parser_defaults(blob_parser=identity) - add_timestamp_parser(session) + add_timestamp_parser(session, parsed_args.v2_debug) diff --git a/tests/functional/test_api_versions.py b/tests/functional/test_api_versions.py index c30da1b6a56a..2c3d339ab1ba 100644 --- a/tests/functional/test_api_versions.py +++ b/tests/functional/test_api_versions.py @@ -10,7 +10,7 @@ # 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.testutils import create_clidriver +from awscli.testutils import create_clidriver, capture_output from awscli.testutils import BaseAWSCommandParamsTest, FileCreator @@ -49,3 +49,19 @@ def test_command_interface_reflects_api_version(self): cmdline = 'ec2 describe-nat-gateways' _, stderr, _ = self.run_cmd(cmdline, expected_rc=2) self.assertIn("Invalid choice: 'describe-nat-gateways'", stderr) + + def test_v2_debug_migration_warning(self): + cmdline = 'ec2 describe-instances --v2-debug' + _, stderr, _ = self.run_cmd(cmdline) + # Make sure that the correct api version is used for the client + # by checking the version that was sent in the request. + self.assertEqual(self.last_params['Version'], self.api_version) + # Make sure that the migration warning is printed since the user + # specified --v2-debug + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 does not support ' + 'calling earlier versions of AWS service APIs via the ' + '`api_versions` configuration file setting.', + stderr + ) + diff --git a/tests/unit/customizations/cloudformation/test_deploy.py b/tests/unit/customizations/cloudformation/test_deploy.py index 8aac8ae3be4f..894bb3dc9aa5 100644 --- a/tests/unit/customizations/cloudformation/test_deploy.py +++ b/tests/unit/customizations/cloudformation/test_deploy.py @@ -120,7 +120,8 @@ def test_command_invoked(self, mock_yaml_parse): None, fake_tags, True, - True + True, + False ) self.deploy_command.parse_key_value_arg.assert_has_calls([ @@ -207,7 +208,8 @@ def test_s3_uploader_is_configured_properly(self, s3UploaderMock, s3UploaderObject, [{"Key": "tagkey1", "Value": "tagvalue1"}], True, - True + True, + False ) s3UploaderMock.assert_called_once_with(mock.ANY, diff --git a/tests/unit/customizations/s3/test_subcommands.py b/tests/unit/customizations/s3/test_subcommands.py index 09dd0a3db667..c0b5af9eb804 100644 --- a/tests/unit/customizations/s3/test_subcommands.py +++ b/tests/unit/customizations/s3/test_subcommands.py @@ -450,7 +450,8 @@ def test_run_cp_put(self): 'paths_type': 'locals3', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': None, 'metadata': None} + 'is_stream': False, 'source_region': None, 'metadata': None, + 'v2_debug': False} config = RuntimeConfig().build_config() cmd_arc = CommandArchitecture(self.session, 'cp', params, config) cmd_arc.set_clients() @@ -470,7 +471,8 @@ def test_error_on_same_line_as_status(self): 'paths_type': 'locals3', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': None, 'metadata': None} + 'is_stream': False, 'source_region': None, 'metadata': None, + 'v2_debug': False} self.http_response.status_code = 400 self.parsed_responses = [{'Error': { 'Code': 'BucketNotExists', @@ -501,7 +503,7 @@ def test_run_cp_get(self): 'paths_type': 's3local', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': None} + 'is_stream': False, 'source_region': None, 'v2_debug': False} self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100, "LastModified": "2014-01-09T20:45:49.000Z"}] config = RuntimeConfig().build_config() @@ -524,7 +526,7 @@ def test_run_cp_copy(self): 'paths_type': 's3s3', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': None} + 'is_stream': False, 'source_region': None, 'v2_debug': False} self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100, "LastModified": "2014-01-09T20:45:49.000Z"}] config = RuntimeConfig().build_config() @@ -548,7 +550,7 @@ def test_run_mv(self): 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, 'is_stream': False, 'source_region': None, - 'is_move': True} + 'is_move': True, 'v2_debug': False} self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100, "LastModified": "2014-01-09T20:45:49.000Z"}] config = RuntimeConfig().build_config() @@ -571,7 +573,8 @@ def test_run_remove(self): 'paths_type': 's3', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': None} + 'is_stream': False, 'source_region': None, + 'v2_debug': False} self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100, "LastModified": "2014-01-09T20:45:49.000Z"}] config = RuntimeConfig().build_config() @@ -598,7 +601,8 @@ def test_run_sync(self): 'paths_type': 'locals3', 'region': 'us-east-1', 'endpoint_url': None, 'verify_ssl': None, 'follow_symlinks': True, 'page_size': None, - 'is_stream': False, 'source_region': 'us-west-2'} + 'is_stream': False, 'source_region': 'us-west-2', + 'v2_debug': False} self.parsed_responses = [ {"CommonPrefixes": [], "Contents": [ {"Key": "text1.txt", "Size": 100, @@ -613,6 +617,31 @@ def test_run_sync(self): output_str = "(dryrun) upload: %s to %s" % (rel_local_file, s3_file) self.assertIn(output_str, self.output.getvalue()) + def test_v2_debug_mv(self): + s3_file = 's3://' + self.bucket + '/' + 'text1.txt' + filters = [['--include', '*']] + params = {'dir_op': False, 'quiet': False, 'dryrun': True, + 'src': s3_file, 'dest': s3_file, 'filters': filters, + 'paths_type': 's3s3', 'region': 'us-east-1', + 'endpoint_url': None, 'verify_ssl': None, + 'follow_symlinks': True, 'page_size': None, + 'is_stream': False, 'source_region': None, + 'is_move': True, 'v2_debug': True} + self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100, + "LastModified": "2014-01-09T20:45:49.000Z"}] + config = RuntimeConfig().build_config() + cmd_arc = CommandArchitecture(self.session, 'mv', params, config) + cmd_arc.set_clients() + cmd_arc.create_instructions() + self.patch_make_request() + cmd_arc.run() + warning_str = 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, object '\ + 'properties will be copied from the source in multipart '\ + 'copies between S3 buckets.' + output_str = f"(dryrun) move: {s3_file} to {s3_file}" + self.assertIn(warning_str, self.err_output.getvalue()) + self.assertIn(output_str, self.output.getvalue()) + class CommandParametersTest(unittest.TestCase): def setUp(self): diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index 8316b7c9229b..f3bf17b4a680 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -14,7 +14,7 @@ from botocore import UNSIGNED import os -from awscli.testutils import mock, unittest +from awscli.testutils import mock, unittest, capture_output from awscli.customizations import globalargs @@ -185,3 +185,63 @@ def test_cli_connect_timeout_for_blocking(self): self.assertEqual(parsed_args.connect_timeout, None) self.assertEqual( session.get_default_client_config().connect_timeout, None) + + def test_v2_debug_python_utf8_env_var(self): + parsed_args = FakeParsedArgs(v2_debug=True) + session = get_session() + environ = {'PYTHONUTF8': '1'} + with mock.patch('os.environ', environ): + with capture_output() as output: + globalargs.detect_migration_breakage(parsed_args, [], session) + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'PYTHONIOENCODING environment variables are unsupported ' + 'in AWS CLI v2.', + output.stderr.getvalue() + ) + + def test_v2_debug_python_utf8_resolved_env_var(self): + parsed_args = FakeParsedArgs(v2_debug=True) + session = get_session() + environ = {'PYTHONUTF8': '1', 'AWS_CLI_FILE_ENCODING': 'UTF-8'} + with mock.patch('os.environ', environ): + with capture_output() as output: + globalargs.detect_migration_breakage(parsed_args, [], session) + self.assertNotIn( + 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'PYTHONIOENCODING environment variables are unsupported ' + 'in AWS CLI v2.', + output.stderr.getvalue() + ) + + def test_v2_debug_python_io_encoding_env_var(self): + parsed_args = FakeParsedArgs(v2_debug=True) + session = get_session() + environ = {'PYTHONIOENCODING': 'UTF8'} + with mock.patch('os.environ', environ): + with capture_output() as output: + globalargs.detect_migration_breakage(parsed_args, [], session) + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'PYTHONIOENCODING environment variables are unsupported ' + 'in AWS CLI v2.', + output.stderr.getvalue() + ) + + def test_v2_debug_s3_sigv2(self): + parsed_args = FakeParsedArgs(v2_debug=True) + session = get_session() + globalargs.detect_migration_breakage(parsed_args, [], session) + with capture_output() as output: + session.emit( + 'choose-signer.s3.*', + signing_name='s3', + region_name='us-west-2', + signature_version='v2', + context={'auth_type': 'v2'}, + ) + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 only uses Signature ' + 'v4 to authenticate Amazon S3 requests.', + output.stderr.getvalue() + ) diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index cb362a0fc631..03eed8e45f30 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -10,10 +10,12 @@ # 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 functools import partial + import pytest from awscli.customizations.paginate import PageArgument -from awscli.testutils import mock, unittest +from awscli.testutils import mock, unittest, capture_output from botocore.exceptions import DataNotFoundError, PaginationError from botocore.model import OperationModel @@ -215,6 +217,7 @@ def setUp(self): self.parsed_args.starting_token = None self.parsed_args.page_size = None self.parsed_args.max_items = None + self.call_parameters = {} def test_should_not_enable_pagination(self): # Here the user has specified a manual pagination argument, @@ -307,6 +310,91 @@ def test_shadowed_args_are_replaced_when_pagination_set_off(self): self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG) +class TestPaginateV2Debug(TestPaginateBase): + def setUp(self): + super().setUp() + self.parsed_globals = mock.Mock() + self.parsed_args = mock.Mock() + self.parsed_args.starting_token = None + self.parsed_args.page_size = None + self.parsed_args.max_items = None + self.call_parameters = {} + + def _mock_emit_first_non_none_response( + self, + mock_input_json_data, + event_name + ): + if event_name == 'get-cli-input-json-data': + return mock_input_json_data + return None + + def test_v2_debug_call_parameters(self): + # Here the user has specified a manual pagination argument, + # via CLI Input JSON and specified v2-debug, so a + # migration warning should be printed. + # From setUp(), the limit_key is 'Bar' + input_tokens = ['Foo', 'Bar'] + self.parsed_globals.v2_debug = True + self.parsed_globals.paginate = True + self.session.emit_first_non_none_response.side_effect = partial( + self._mock_emit_first_non_none_response, + {'Bar': 10} + ) + # Corresponds to --bar 10 + self.call_parameters['Foo'] = None + self.call_parameters['Bar'] = 10 + with capture_output() as output: + paginate.check_should_enable_pagination_call_parameters( + self.session, + input_tokens, + self.call_parameters, + {}, + self.parsed_globals + ) + # We should have printed the migration warning + # because the user specified {Bar: 10} in the input JSON + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'pagination parameters by using a file with the ' + '`--cli-input-json` parameter, automatic pagination will be ' + 'turned off.', + output.stderr.getvalue() + ) + + def test_v2_debug_call_params_does_not_print_for_cmd_args(self): + # Here the user has specified a pagination argument as a command + # argument and specified v2-debug, so the migration warning should NOT + # be printed. From setUp(), the limit_key is 'Bar' + input_tokens = ['Foo', 'Bar'] + self.parsed_globals.v2_debug = True + self.parsed_globals.paginate = True + self.session.emit_first_non_none_response.side_effect = partial( + self._mock_emit_first_non_none_response, + None + ) + # Corresponds to --bar 10 + self.call_parameters['Foo'] = None + self.call_parameters['Bar'] = 10 + with capture_output() as output: + paginate.check_should_enable_pagination_call_parameters( + self.session, + input_tokens, + self.call_parameters, + {}, + self.parsed_globals + ) + # We should not have printed the warning because + # the user did not specify any params through CLI input JSON + self.assertNotIn( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'pagination parameters by using a file with the ' + '`--cli-input-json` parameter, automatic pagination will be ' + 'turned off.', + output.stderr.getvalue() + ) + + class TestEnsurePagingParamsNotSet(TestPaginateBase): def setUp(self): super(TestEnsurePagingParamsNotSet, self).setUp() diff --git a/tests/unit/customizations/test_scalarparse.py b/tests/unit/customizations/test_scalarparse.py index 0dd0afb53d37..f04588396e3c 100644 --- a/tests/unit/customizations/test_scalarparse.py +++ b/tests/unit/customizations/test_scalarparse.py @@ -32,7 +32,7 @@ def test_scalar_parsers_set(self): session = mock.Mock() session.get_scoped_config.return_value = {'cli_timestamp_format': 'none'} - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) session.get_component.assert_called_with('response_parser_factory') factory = session.get_component.return_value expected = [mock.call(blob_parser=scalarparse.identity), @@ -45,7 +45,7 @@ def test_choose_none_timestamp_formatter(self): session.get_scoped_config.return_value = {'cli_timestamp_format': 'none'} factory = session.get_component.return_value - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) factory.set_parser_defaults.assert_called_with( timestamp_parser=scalarparse.identity) @@ -54,7 +54,7 @@ def test_choose_iso_timestamp_formatter(self): session.get_scoped_config.return_value = {'cli_timestamp_format': 'iso8601'} factory = session.get_component.return_value - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) factory.set_parser_defaults.assert_called_with( timestamp_parser=scalarparse.iso_format) @@ -64,12 +64,12 @@ def test_choose_invalid_timestamp_formatter(self): 'foobar'} session.get_component.return_value with self.assertRaises(ValueError): - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) def test_choose_timestamp_parser_profile_not_found(self): session = mock.Mock(spec=Session) session.get_scoped_config.side_effect = ProfileNotFound(profile='foo') factory = session.get_component.return_value - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) factory.set_parser_defaults.assert_called_with( timestamp_parser=scalarparse.identity) diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py index 1b5e2dfa68ec..a88e13ed76ed 100644 --- a/tests/unit/test_clidriver.py +++ b/tests/unit/test_clidriver.py @@ -104,7 +104,8 @@ GET_VARIABLE = { 'provider': 'aws', 'output': 'json', - 'api_versions': {} + 'api_versions': {}, + 'cli_binary_format': 'raw-in-base64-out', } @@ -236,6 +237,9 @@ def get_config_variable(self, name): return GET_VARIABLE[name] return self.session_vars[name] + def get_scoped_config(self): + return GET_VARIABLE + def get_service_model(self, name, api_version=None): return botocore.model.ServiceModel( MINI_SERVICE, service_name='s3') From f9963e857cc66f5af4211aae25c8b3bab458e4cd Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 22 Oct 2025 12:53:45 -0400 Subject: [PATCH 09/40] Update unit test based on upstream changes in `develop`. --- tests/unit/customizations/test_scalarparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/customizations/test_scalarparse.py b/tests/unit/customizations/test_scalarparse.py index 81c518d17927..b5cbfd10be23 100644 --- a/tests/unit/customizations/test_scalarparse.py +++ b/tests/unit/customizations/test_scalarparse.py @@ -54,7 +54,7 @@ def test_choose_wire_timestamp_formatter(self): session.get_scoped_config.return_value = {'cli_timestamp_format': 'wire'} factory = session.get_component.return_value - scalarparse.add_scalar_parsers(session) + scalarparse.add_scalar_parsers(session, mock.Mock(v2_debug=False)) factory.set_parser_defaults.assert_called_with( timestamp_parser=scalarparse.identity) From 5300c219fb63810dafeb3acff60cad37b9db5eef Mon Sep 17 00:00:00 2001 From: aemous Date: Thu, 9 Oct 2025 10:51:05 -0400 Subject: [PATCH 10/40] Add tests and pagination updates. --- tests/unit/customizations/test_paginate.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index 03eed8e45f30..aa0ef41f9d56 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -234,6 +234,31 @@ def test_should_not_enable_pagination(self): # user specified --bar 10 self.assertFalse(self.parsed_globals.paginate) + def test_should_not_enable_pagination_call_parameters(self): + # Here the user has specified a manual pagination argument, + # via CLI Input JSON and specified v2-debug, so a + # migration warning should printed. + # From setUp(), the limit_key is 'Bar' + input_tokens = ['Foo', 'Bar'] + self.parsed_globals.v2_debug = True + self.parsed_globals.paginate = True + # Corresponds to --bar 10 + self.call_parameters['Foo'] = None + self.call_parameters['Bar'] = 10 + with capture_output() as output: + paginate.check_should_enable_pagination_call_parameters( + input_tokens, self.call_parameters, {}, self.parsed_globals + ) + # We should have printed the migration warning + # because the user specified {Bar: 10} in the input JSON + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'pagination parameters by using a file with the ' + '`--cli-input-json` parameter, automatic pagination will be ' + 'turned off.', + output.stdout.getvalue() + ) + def test_should_enable_pagination_with_no_args(self): input_tokens = ['foo', 'bar'] self.parsed_globals.paginate = True From 5c865bd440e4cff494d3dac0f0799d66d52a9a5b Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 10 Oct 2025 14:08:19 -0400 Subject: [PATCH 11/40] Switch migration warnings to use stderr. --- tests/unit/customizations/test_paginate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index aa0ef41f9d56..959bf3276d4c 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -234,7 +234,7 @@ def test_should_not_enable_pagination(self): # user specified --bar 10 self.assertFalse(self.parsed_globals.paginate) - def test_should_not_enable_pagination_call_parameters(self): + def test_v2_debug_call_parameters(self): # Here the user has specified a manual pagination argument, # via CLI Input JSON and specified v2-debug, so a # migration warning should printed. @@ -256,7 +256,7 @@ def test_should_not_enable_pagination_call_parameters(self): 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' 'turned off.', - output.stdout.getvalue() + output.stderr.getvalue() ) def test_should_enable_pagination_with_no_args(self): From 0e4b4e9597c2b8e773be47aca4236170f62fe034 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 14 Oct 2025 14:47:57 -0400 Subject: [PATCH 12/40] Revise based on feedback. --- tests/unit/customizations/test_paginate.py | 25 ---------------------- 1 file changed, 25 deletions(-) diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index 959bf3276d4c..03eed8e45f30 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -234,31 +234,6 @@ def test_should_not_enable_pagination(self): # user specified --bar 10 self.assertFalse(self.parsed_globals.paginate) - def test_v2_debug_call_parameters(self): - # Here the user has specified a manual pagination argument, - # via CLI Input JSON and specified v2-debug, so a - # migration warning should printed. - # From setUp(), the limit_key is 'Bar' - input_tokens = ['Foo', 'Bar'] - self.parsed_globals.v2_debug = True - self.parsed_globals.paginate = True - # Corresponds to --bar 10 - self.call_parameters['Foo'] = None - self.call_parameters['Bar'] = 10 - with capture_output() as output: - paginate.check_should_enable_pagination_call_parameters( - input_tokens, self.call_parameters, {}, self.parsed_globals - ) - # We should have printed the migration warning - # because the user specified {Bar: 10} in the input JSON - self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' - 'pagination parameters by using a file with the ' - '`--cli-input-json` parameter, automatic pagination will be ' - 'turned off.', - output.stderr.getvalue() - ) - def test_should_enable_pagination_with_no_args(self): input_tokens = ['foo', 'bar'] self.parsed_globals.paginate = True From ad991778e20d002d4e69a69216d3a72298b50a68 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 13 Oct 2025 18:35:19 -0400 Subject: [PATCH 13/40] Add tests to verify register feature ID. --- awscli/customizations/globalargs.py | 13 +++++++ requirements.txt | 3 +- tests/unit/customizations/test_globalargs.py | 40 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 96b599ce37ea..c8bc83fe4ea6 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -18,10 +18,12 @@ from botocore.client import Config from botocore import UNSIGNED from botocore.endpoint import DEFAULT_TIMEOUT +from botocore.useragent import register_feature_id import jmespath from awscli.compat import urlparse + def register_parse_global_args(cli): cli.register('top-level-args-parsed', resolve_types, unique_id='resolve-types') @@ -186,8 +188,19 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): '/cliv2-migration-changes.html#cliv2-migration-aliases.\n', out_file=sys.stderr ) + # Register against the provide-client-params event to ensure that the + # feature ID is registered before any API requests are made. We + # cannot register the feature ID in this function because no + # botocore context is created at this point. + session.register( + 'provide-client-params.*.*', + _register_v2_debug_feature_id + ) session.register('choose-signer.s3.*', warn_if_sigv2) +def _register_v2_debug_feature_id(params, model, **kwargs): + register_feature_id('CLI_V1_TO_V2_MIGRATION_DEBUG_MODE') + def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): # The regional us-east-1 endpoint is used in certain cases (e.g. # FIPS/Dual-Stack is enabled). Rather than duplicating this logic diff --git a/requirements.txt b/requirements.txt index 1802a0285af9..2d1dc01ec558 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # botocore and the awscli packages are typically developed # in tandem, so we're requiring the latest develop # branch of botocore and s3transfer when working on the awscli. --e git+https://github.com/boto/botocore.git@develop#egg=botocore +# -e git+https://github.com/boto/botocore.git@develop#egg=botocore +-e file:///Users/aemous/GitHub/botocore -e git+https://github.com/boto/s3transfer.git@develop#egg=s3transfer diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index f3bf17b4a680..5fb8d93c4fbe 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -10,6 +10,8 @@ # 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 unittest.mock import patch + from botocore.session import get_session from botocore import UNSIGNED import os @@ -186,6 +188,44 @@ def test_cli_connect_timeout_for_blocking(self): self.assertEqual( session.get_default_client_config().connect_timeout, None) + def test_register_feature_id(self): + parsed_args = FakeParsedArgs(v2_debug=True) + session = get_session() + globalargs.detect_migration_breakage( + parsed_args, + [], + session + ) + # Verify the correct feature ID is registered during the + # provide-client-params event. + with (mock.patch('awscli.customizations.globalargs.register_feature_id') + as mock_register_feature_id): + session.emit( + 'provide-client-params.s3.ListBuckets', + params={}, + model={}, + ) + mock_register_feature_id.assert_any_call( + 'CLI_V1_TO_V2_MIGRATION_DEBUG_MODE' + ) + + def test_ecr_login_v2_debug(self): + parsed_args = FakeParsedArgs(command='ecr', v2_debug=True) + remaining_args = ['get-login'] + session = get_session() + with capture_output() as output: + globalargs.detect_migration_breakage( + parsed_args, + remaining_args, + session + ) + # Verify the expected warning is printed + self.assertIn( + 'AWS CLI v2 MIGRATION WARNING: The ecr get-login command has ' + 'been removed in AWS CLI v2.', + output.stderr.getvalue() + ) + def test_v2_debug_python_utf8_env_var(self): parsed_args = FakeParsedArgs(v2_debug=True) session = get_session() From d407cb7b6aa9142a42f283b640e7c3db85468c79 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 15 Oct 2025 14:27:48 -0400 Subject: [PATCH 14/40] Progress. --- awscli/customizations/globalargs.py | 15 +++++++++++++++ awscli/data/cli.json | 5 +++++ awscli/examples/global_options.rst | 4 ++++ awscli/examples/global_synopsis.rst | 1 + tests/unit/test_clidriver.py | 5 ----- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index c8bc83fe4ea6..f1be8214c281 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -106,6 +106,20 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ] region = parsed_args.region or session.get_config_variable('region') s3_config = session.get_config_variable('s3') + if ( + not session.get_scoped_config().get('cli_pager', None) + == '' and 'AWS_PAGER' not in os.environ + ): + uni_print( + 'AWS CLI v2 UPGRADE WARNING: By default, the AWS CLI version 2 ' + 'returns all output through your operating system’s default pager ' + 'program. To retain AWS CLI v1 behavior, set the `cli_pager` ' + 'configuration setting, or the `AWS_PAGER` environment variable, ' + 'to the empty string. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-output-pager.\n', + out_file=sys.stderr + ) if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ: if 'AWS_CLI_FILE_ENCODING' not in os.environ: uni_print( @@ -198,6 +212,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) session.register('choose-signer.s3.*', warn_if_sigv2) + def _register_v2_debug_feature_id(params, model, **kwargs): register_feature_id('CLI_V1_TO_V2_MIGRATION_DEBUG_MODE') diff --git a/awscli/data/cli.json b/awscli/data/cli.json index 25687399d05c..d75489e88664 100644 --- a/awscli/data/cli.json +++ b/awscli/data/cli.json @@ -69,6 +69,11 @@ "action": "store_true", "dest": "v2_debug", "help": "

Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected.

" + }, + "no-v2-debug": { + "action": "store_true", + "dest": "no_v2_debug", + "help": "

Disable AWS CLI v2 migration assistance.

" } } } diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index d450cf0ce78a..914d070ed329 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -74,3 +74,7 @@ Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected. +``--no-v2-debug`` (boolean) + + Disable AWS CLI v2 migration assistance. + diff --git a/awscli/examples/global_synopsis.rst b/awscli/examples/global_synopsis.rst index 12865958a809..1d8295f550ed 100644 --- a/awscli/examples/global_synopsis.rst +++ b/awscli/examples/global_synopsis.rst @@ -13,3 +13,4 @@ [--cli-read-timeout ] [--cli-connect-timeout ] [--v2-debug] +[--no-v2-debug] diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py index a88e13ed76ed..bfc17cf71ab8 100644 --- a/tests/unit/test_clidriver.py +++ b/tests/unit/test_clidriver.py @@ -92,11 +92,6 @@ "type": "int", "help": "" }, - "migrate-v2": { - "action": "store_true", - "dest": "migrate_v2", - "help": "", - } } }, } From 25a2e63d3c59e9fa00f7641dd3e9a8ea94b9555e Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 08:01:03 -0400 Subject: [PATCH 15/40] Progress. --- awscli/clidriver.py | 6 +- .../customizations/cloudformation/deploy.py | 7 +- awscli/customizations/globalargs.py | 65 ++++++++++--------- awscli/customizations/paginate.py | 11 ++-- awscli/customizations/s3/subcommands.py | 6 +- awscli/customizations/scalarparse.py | 14 ++-- awscli/utils.py | 12 ++++ tests/functional/test_api_versions.py | 2 +- .../customizations/s3/test_subcommands.py | 2 +- tests/unit/customizations/test_globalargs.py | 29 +++++++-- tests/unit/customizations/test_paginate.py | 5 +- tests/unit/test_utils.py | 28 +++++++- 12 files changed, 126 insertions(+), 61 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 1a798aa2c5c0..8f8a897df6ea 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -49,7 +49,7 @@ ServiceHelpCommand, ) from awscli.plugin import load_plugins -from awscli.utils import emit_top_level_args_parsed_event, write_exception, create_nested_client +from awscli.utils import emit_top_level_args_parsed_event, write_exception, create_nested_client, resolve_v2_debug_mode from botocore import __version__ as botocore_version from botocore import xform_name @@ -682,7 +682,7 @@ def _detect_binary_file_migration_change( # if cli_binary_format is set to raw-in-base64-out, then v2 behavior will # be the same as v1, so there is no breaking change in this case. return - if parsed_globals.v2_debug: + if resolve_v2_debug_mode(parsed_globals): parsed_args_to_check = { arg: getattr(parsed_args, arg) for arg in vars(parsed_args) if getattr(parsed_args, arg) @@ -696,7 +696,7 @@ def _detect_binary_file_migration_change( ] if arg_values_to_check: uni_print( - 'AWS CLI v2 MIGRATION WARNING: When specifying a blob-type ' + 'AWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' 'parameter starting with `file://`, AWS CLI v2 will assume ' 'the content of the file is already base64-encoded. To ' 'maintain v1 behavior after upgrading to v2, set the ' diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index a734453e2e0f..e212cd4815a3 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -25,7 +25,7 @@ from awscli.customizations.commands import BasicCommand from awscli.compat import get_stdout_text_writer from awscli.customizations.utils import uni_print -from awscli.utils import create_nested_client, write_exception +from awscli.utils import create_nested_client, write_exception, resolve_v2_debug_mode LOG = logging.getLogger(__name__) @@ -317,12 +317,13 @@ def _run_main(self, parsed_args, parsed_globals): s3_uploader = None deployer = Deployer(cloudformation_client) + v2_debug = resolve_v2_debug_mode(parsed_globals) return self.deploy(deployer, stack_name, template_str, parameters, parsed_args.capabilities, parsed_args.execute_changeset, parsed_args.role_arn, parsed_args.notification_arns, s3_uploader, tags, parsed_args.fail_on_empty_changeset, - parsed_args.disable_rollback, getattr(parsed_globals, 'v2_debug', False)) + parsed_args.disable_rollback, v2_debug) def deploy(self, deployer, stack_name, template_str, parameters, capabilities, execute_changeset, role_arn, @@ -344,7 +345,7 @@ def deploy(self, deployer, stack_name, template_str, if fail_on_empty_changeset: if v2_debug: uni_print( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' 'deploying an AWS CloudFormation Template that ' 'results in an empty changeset will NOT result in an ' 'error. You can add the -–no-fail-on-empty-changeset ' diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index f1be8214c281..93633c580b83 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -22,6 +22,7 @@ import jmespath from awscli.compat import urlparse +from awscli.utils import resolve_v2_debug_mode def register_parse_global_args(cli): @@ -98,7 +99,7 @@ def resolve_cli_connect_timeout(parsed_args, session, **kwargs): _resolve_timeout(session, parsed_args, arg_name) def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): - if not parsed_args.v2_debug: + if not resolve_v2_debug_mode(parsed_args): return url_params = [ param for param in remaining_args @@ -113,21 +114,22 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): uni_print( 'AWS CLI v2 UPGRADE WARNING: By default, the AWS CLI version 2 ' 'returns all output through your operating system’s default pager ' - 'program. To retain AWS CLI v1 behavior, set the `cli_pager` ' - 'configuration setting, or the `AWS_PAGER` environment variable, ' - 'to the empty string. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' - '#cliv2-migration-output-pager.\n', + 'program. To retain AWS CLI v1 behavior after upgrading to AWS ' + 'CLI v2, set the `cli_pager` configuration setting, or the ' + '`AWS_PAGER` environment variable, to the empty string. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide' + '/cliv2-migration-changes.html#cliv2-migration-output-pager.\n', out_file=sys.stderr ) if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ: if 'AWS_CLI_FILE_ENCODING' not in os.environ: uni_print( - 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' 'PYTHONIOENCODING environment variables are unsupported ' - 'in AWS CLI v2. AWS CLI v2 uses AWS_CLI_FILE_ENCODING ' - 'instead, set this environment variable to resolve this. ' - 'See https://docs.aws.amazon.com/cli/latest/userguide/' + 'in AWS CLI v2. AWS CLI v2 uses the `AWS_CLI_FILE_ENCODING` ' + 'variable instead; set this environment variable to retain ' + 'AWS CLI v1 behavior after upgrading to AWS CLI v2. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' '#cliv2-migration-encodingenvvar.\n', out_file=sys.stderr @@ -145,18 +147,18 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if session.get_config_variable('api_versions'): uni_print( - 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 does not support ' + 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' 'calling earlier versions of AWS service APIs via the ' '`api_versions` configuration file setting. To migrate to v2 ' - 'behavior and resolve this warning, remove the `api_versions` ' - 'setting in the configuration file. See ' + 'behavior, remove the `api_versions` configuration setting, and ' + 'test against the latest API versions. See ' 'https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html#cliv2-migration-api-versions.\n', out_file = sys.stderr ) if session.full_config.get('plugins', {}): uni_print( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, plugin support ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugin support ' 'is provisional. If you rely on plugins, be sure to lock into ' 'a particular version of the AWS CLI and test the ' 'functionality of your plugins for each upgrade. See ' @@ -167,18 +169,18 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': uni_print( - 'AWS CLI v2 MIGRATION WARNING: The ecr get-login command has ' - 'been removed in AWS CLI v2. See https://docs.aws.amazon.com/' - 'cli/latest/userguide/cliv2-migration-changes.html' - '#cliv2-migration-ecr-get-login.\n', + 'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has ' + 'been removed in AWS CLI v2. You must use `ecr get-login-password` ' + 'instead. See https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n', out_file=sys.stderr ) - if url_params and session.full_config.get('cli_follow_urlparam', True): + if url_params and session.get_scoped_config().get('cli_pager', None): uni_print( - 'AWS CLI v2 MIGRATION WARNING: For input parameters that have ' + 'AWS CLI v2 UPGRADE WARNING: For input parameters that have ' 'a prefix of http:// or https://, AWS CLI v2 will no longer ' 'automatically request the content of the URL for the ' - 'parameter, and the cli_follow_urlparam option has been ' + 'parameter, and the `cli_follow_urlparam` option has been ' 'removed. See https://docs.aws.amazon.com/cli/latest/' 'userguide/cliv2-migration-changes.html' '#cliv2-migration-paramfile.\n', @@ -195,9 +197,10 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): and f"--{working_param}" in remaining_args ): uni_print( - 'AWS CLI v2 MIGRATION WARNING: You have entered command ' + 'AWS CLI v2 UPGRADE WARNING: You have entered command ' 'arguments that uses at least 1 of 21 hidden aliases that ' - 'were removed in AWS CLI v2. See ' + 'were removed in AWS CLI v2. You must replace usage of the ' + 'obsolete alias with the corresponding working parameter. See ' 'https://docs.aws.amazon.com/cli/latest/userguide' '/cliv2-migration-changes.html#cliv2-migration-aliases.\n', out_file=sys.stderr @@ -222,12 +225,12 @@ def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): # from botocore, we check the endpoint URL directly. if 's3.amazonaws.com' in request.url: uni_print( - 'AWS CLI v2 MIGRATION WARNING: When you configure AWS CLI v2 ' + 'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' 'to use the `us-east-1` region, it uses the true regional ' - 'endpoint rather than the global endpoint. To continue using ' - 'the global endpoint on v2, configure the region to be ' - '`aws-global`. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' + 'endpoint rather than the global endpoint. To retain AWS CLI v1 ' + 'behavior after upgrading to AWS CLI v2, configure the `region` ' + 'setting to `aws-global`. See https://docs.aws.amazon.com/cli' + '/latest/userguide/cliv2-migration-changes.html' '#cliv2-migration-s3-regional-endpoint.\n', out_file=sys.stderr ) @@ -242,9 +245,9 @@ def warn_if_sigv2( if context.get('auth_type', None) == 'v2': uni_print( 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 only uses Signature ' - 'v4 to authenticate Amazon S3 requests. Run the command `aws ' - 'configure set s3.signature_version s3v4` to migrate to v4 and ' - 'resolve this.\n', + 'v4 to authenticate Amazon S3 requests. To migrate to AWS CLI ' + 'v2 behavior, configure the Signature Version S3 setting to ' + 'version 4.\n', out_file=sys.stderr ) diff --git a/awscli/customizations/paginate.py b/awscli/customizations/paginate.py index 836b338b0fef..2d64bae611c3 100644 --- a/awscli/customizations/paginate.py +++ b/awscli/customizations/paginate.py @@ -33,7 +33,7 @@ from botocore import model from awscli.arguments import BaseCLIArgument - +from awscli.utils import resolve_v2_debug_mode logger = logging.getLogger(__name__) @@ -293,7 +293,7 @@ def check_should_enable_pagination_call_parameters( naming space and would be missed by the processing above. This function gets called on the calling-command event. """ - if parsed_globals.v2_debug: + if resolve_v2_debug_mode(parsed_globals): cli_input_json_data = session.emit_first_non_none_response( f"get-cli-input-json-data", ) @@ -304,11 +304,12 @@ def check_should_enable_pagination_call_parameters( ] if pagination_params_in_input_tokens: uni_print( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' - 'turned off. This is not the case in v1. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'turned off. To retain AWS CLI v1 behavior after upgrading to ' + 'AWS CLI v2, remove all pagination parameters from the input ' + 'JSON. See https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' '#cliv2-migration-skeleton-paging.\n', out_file=sys.stderr diff --git a/awscli/customizations/s3/subcommands.py b/awscli/customizations/s3/subcommands.py index 665fa9ae2603..b05ae1857020 100644 --- a/awscli/customizations/s3/subcommands.py +++ b/awscli/customizations/s3/subcommands.py @@ -36,7 +36,7 @@ from awscli.customizations.s3.syncstrategy.base import MissingFileSync, \ SizeAndLastModifiedSync, NeverSync from awscli.customizations.s3 import transferconfig - +from awscli.utils import resolve_v2_debug_mode LOGGER = logging.getLogger(__name__) @@ -1060,7 +1060,7 @@ def run(self): if self.parameters['v2_debug']: if operation_name == 'copy': uni_print( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, object ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object ' 'properties will be copied from the source in multipart ' 'copies between S3 buckets. This may result in extra S3 ' 'API calls being made. Breakage may occur if the principal ' @@ -1464,7 +1464,7 @@ def add_page_size(self, parsed_args): self.parameters['page_size'] = getattr(parsed_args, 'page_size', None) def add_v2_debug(self, parsed_globals): - self.parameters['v2_debug'] = parsed_globals.v2_debug + self.parameters['v2_debug'] = resolve_v2_debug_mode(parsed_globals) def _validate_sse_c_args(self): self._validate_sse_c_arg() diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index a9c56eaa8656..ba90b0667085 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -33,6 +33,7 @@ from botocore.exceptions import ProfileNotFound from awscli.customizations.utils import uni_print +from awscli.utils import resolve_v2_debug_mode def register_scalar_parser(event_handlers): @@ -79,12 +80,13 @@ def identity_with_warning(x): if not encountered_timestamp: encountered_timestamp = True uni_print( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, all timestamp ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, all timestamp ' 'response values are returned in the ISO 8601 format. To ' - 'migrate to v2 behavior and resolve this warning, set the ' - 'configuration variable `cli_timestamp_format` to `iso8601`. ' - 'See https://docs.aws.amazon.com/cli/latest/userguide/' - 'cliv2-migration-changes.html#cliv2-migration-timestamp.\n', + 'migrate to v2 behavior, set the configuration variable ' + '`cli_timestamp_format` to `iso8601`. See https://' + 'docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' + '#cliv2-migration-timestamp.\n', out_file=sys.stderr ) return identity(x) @@ -101,4 +103,4 @@ def identity_with_warning(x): def add_scalar_parsers(session, parsed_args, **kwargs): factory = session.get_component('response_parser_factory') factory.set_parser_defaults(blob_parser=identity) - add_timestamp_parser(session, parsed_args.v2_debug) + add_timestamp_parser(session, resolve_v2_debug_mode(parsed_args)) diff --git a/awscli/utils.py b/awscli/utils.py index 3e7e9d6e8e37..4e4809a5f166 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -134,6 +134,18 @@ def find_service_and_method_in_event_name(event_name): return service_name, operation_name +def resolve_v2_debug_mode(args): + # Resolve whether v2-debug mode is enabled, + # following the correct precedence order. + if no_v2_debug := getattr(args, 'no_v2_debug', False): + return False + if getattr(args, 'v2_debug', False): + return True + if os.environ.get('AWS_CLI_UPGRADE_DEBUG_MODE', '').lower() == 'true': + return True + return False + + def is_document_type(shape): """Check if shape is a document type""" return getattr(shape, 'is_document_type', False) diff --git a/tests/functional/test_api_versions.py b/tests/functional/test_api_versions.py index 2c3d339ab1ba..e98f0159cb44 100644 --- a/tests/functional/test_api_versions.py +++ b/tests/functional/test_api_versions.py @@ -59,7 +59,7 @@ def test_v2_debug_migration_warning(self): # Make sure that the migration warning is printed since the user # specified --v2-debug self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 does not support ' + 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' 'calling earlier versions of AWS service APIs via the ' '`api_versions` configuration file setting.', stderr diff --git a/tests/unit/customizations/s3/test_subcommands.py b/tests/unit/customizations/s3/test_subcommands.py index c0b5af9eb804..b19d65a352ed 100644 --- a/tests/unit/customizations/s3/test_subcommands.py +++ b/tests/unit/customizations/s3/test_subcommands.py @@ -635,7 +635,7 @@ def test_v2_debug_mv(self): cmd_arc.create_instructions() self.patch_make_request() cmd_arc.run() - warning_str = 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, object '\ + warning_str = 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object '\ 'properties will be copied from the source in multipart '\ 'copies between S3 buckets.' output_str = f"(dryrun) move: {s3_file} to {s3_file}" diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index 5fb8d93c4fbe..1157563789dd 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -221,7 +221,26 @@ def test_ecr_login_v2_debug(self): ) # Verify the expected warning is printed self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: The ecr get-login command has ' + 'AWS CLI v2 UPGRADE WARNING: The ecr get-login command has ' + 'been removed in AWS CLI v2.', + output.stderr.getvalue() + ) + + def test_ecr_login_v2_debug_env_var(self): + parsed_args = FakeParsedArgs(command='ecr') + remaining_args = ['get-login'] + session = get_session() + env = {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'} + with capture_output() as output: + with mock.patch('os.environ', env): + globalargs.detect_migration_breakage( + parsed_args, + remaining_args, + session + ) + # Verify the expected warning is printed + self.assertIn( + 'AWS CLI v2 UPGRADE WARNING: The ecr get-login command has ' 'been removed in AWS CLI v2.', output.stderr.getvalue() ) @@ -234,7 +253,7 @@ def test_v2_debug_python_utf8_env_var(self): with capture_output() as output: globalargs.detect_migration_breakage(parsed_args, [], session) self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' 'PYTHONIOENCODING environment variables are unsupported ' 'in AWS CLI v2.', output.stderr.getvalue() @@ -248,7 +267,7 @@ def test_v2_debug_python_utf8_resolved_env_var(self): with capture_output() as output: globalargs.detect_migration_breakage(parsed_args, [], session) self.assertNotIn( - 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' 'PYTHONIOENCODING environment variables are unsupported ' 'in AWS CLI v2.', output.stderr.getvalue() @@ -262,7 +281,7 @@ def test_v2_debug_python_io_encoding_env_var(self): with capture_output() as output: globalargs.detect_migration_breakage(parsed_args, [], session) self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: The PYTHONUTF8 and ' + 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' 'PYTHONIOENCODING environment variables are unsupported ' 'in AWS CLI v2.', output.stderr.getvalue() @@ -281,7 +300,7 @@ def test_v2_debug_s3_sigv2(self): context={'auth_type': 'v2'}, ) self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 only uses Signature ' + 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' 'v4 to authenticate Amazon S3 requests.', output.stderr.getvalue() ) diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index 03eed8e45f30..2b0a1ed818d4 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -335,6 +335,7 @@ def test_v2_debug_call_parameters(self): # migration warning should be printed. # From setUp(), the limit_key is 'Bar' input_tokens = ['Foo', 'Bar'] + self.parsed_globals.no_v2_debug = False self.parsed_globals.v2_debug = True self.parsed_globals.paginate = True self.session.emit_first_non_none_response.side_effect = partial( @@ -355,7 +356,7 @@ def test_v2_debug_call_parameters(self): # We should have printed the migration warning # because the user specified {Bar: 10} in the input JSON self.assertIn( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' 'turned off.', @@ -387,7 +388,7 @@ def test_v2_debug_call_params_does_not_print_for_cmd_args(self): # We should not have printed the warning because # the user did not specify any params through CLI input JSON self.assertNotIn( - 'AWS CLI v2 MIGRATION WARNING: In AWS CLI v2, if you specify ' + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' 'turned off.', diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 8030e2eba786..3f979d8fd25d 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -23,7 +23,7 @@ split_on_commas, ignore_ctrl_c, find_service_and_method_in_event_name, is_document_type, is_document_type_container, is_streaming_blob_type, is_tagged_union_type, operation_uses_document_types, ShapeWalker, - ShapeRecordingVisitor, OutputStreamFactory + ShapeRecordingVisitor, OutputStreamFactory, resolve_v2_debug_mode ) @@ -135,6 +135,32 @@ def test_returns_none_if_event_is_too_short(self): self.assertIs(operation, None) +class TestV2DebugResolution(unittest.TestCase): + def test_no_v2_debug_flag_takes_precedence(self): + args = mock.Mock(no_v2_debug=True, v2_debug=True) + with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'}): + self.assertFalse(resolve_v2_debug_mode(args)) + + def test_v2_debug_flag_enabled(self): + args = mock.Mock(no_v2_debug=False, v2_debug=True) + self.assertTrue(resolve_v2_debug_mode(args)) + + def test_env_var_enabled(self): + args = mock.Mock(no_v2_debug=False, v2_debug=False) + with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'}): + self.assertTrue(resolve_v2_debug_mode(args)) + + def test_all_disabled(self): + args = mock.Mock(no_v2_debug=False, v2_debug=False) + with mock.patch.dict(os.environ, {}, clear=True): + self.assertFalse(resolve_v2_debug_mode(args)) + + def test_env_var_non_true_value(self): + args = mock.Mock(no_v2_debug=False, v2_debug=False) + with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'false'}): + self.assertFalse(resolve_v2_debug_mode(args)) + + class MockProcess(object): @property def stdin(self): From 74fb5c2968cd494a70cbc8f99fd9a6d011db58be Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 10:12:43 -0400 Subject: [PATCH 16/40] Make cfn deploy false-positive category to reduce change of service-dependent false-negatives. --- .../customizations/cloudformation/deploy.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index e212cd4815a3..e33b03a8f362 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -331,6 +331,18 @@ def deploy(self, deployer, stack_name, template_str, fail_on_empty_changeset=True, disable_rollback=False, v2_debug=False): try: + if v2_debug and fail_on_empty_changeset: + uni_print( + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' + 'deploying an AWS CloudFormation Template that ' + 'results in an empty changeset will NOT result in an ' + 'error. You can add the -–no-fail-on-empty-changeset ' + 'flag to migrate to v2 behavior and resolve this ' + 'warning. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-cfn.\n', + out_file=sys.stderr + ) result = deployer.create_and_wait_for_changeset( stack_name=stack_name, cfn_template=template_str, @@ -343,18 +355,6 @@ def deploy(self, deployer, stack_name, template_str, ) except exceptions.ChangeEmptyError as ex: if fail_on_empty_changeset: - if v2_debug: - uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' - 'deploying an AWS CloudFormation Template that ' - 'results in an empty changeset will NOT result in an ' - 'error. You can add the -–no-fail-on-empty-changeset ' - 'flag to migrate to v2 behavior and resolve this ' - 'warning. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' - '#cliv2-migration-cfn.\n', - out_file=sys.stderr - ) raise write_exception(ex, outfile=get_stdout_text_writer()) return 0 From 37d656dfb636669a74ff2dd0d5a061e34f2ee186 Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 10:25:11 -0400 Subject: [PATCH 17/40] Progress --- awscli/customizations/globalargs.py | 2 +- tests/unit/customizations/test_globalargs.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 93633c580b83..59582ab61768 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -244,7 +244,7 @@ def warn_if_sigv2( ): if context.get('auth_type', None) == 'v2': uni_print( - 'AWS CLI v2 MIGRATION WARNING: The AWS CLI v2 only uses Signature ' + 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' 'v4 to authenticate Amazon S3 requests. To migrate to AWS CLI ' 'v2 behavior, configure the Signature Version S3 setting to ' 'version 4.\n', diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index 1157563789dd..4ff130b9d1f7 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -221,7 +221,7 @@ def test_ecr_login_v2_debug(self): ) # Verify the expected warning is printed self.assertIn( - 'AWS CLI v2 UPGRADE WARNING: The ecr get-login command has ' + 'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has ' 'been removed in AWS CLI v2.', output.stderr.getvalue() ) @@ -240,7 +240,7 @@ def test_ecr_login_v2_debug_env_var(self): ) # Verify the expected warning is printed self.assertIn( - 'AWS CLI v2 UPGRADE WARNING: The ecr get-login command has ' + 'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has ' 'been removed in AWS CLI v2.', output.stderr.getvalue() ) From 6a6a1ce86542e8507596bef84253f008e404b627 Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 09:42:20 -0400 Subject: [PATCH 18/40] Add 'wire' as a valid value for cli_timestamp_format. --- .changes/next-release/enhancement-timestamps-52310.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/next-release/enhancement-timestamps-52310.json diff --git a/.changes/next-release/enhancement-timestamps-52310.json b/.changes/next-release/enhancement-timestamps-52310.json new file mode 100644 index 000000000000..eeb8c3350e93 --- /dev/null +++ b/.changes/next-release/enhancement-timestamps-52310.json @@ -0,0 +1,5 @@ +{ + "type": "enhancement", + "category": "timestamps", + "description": "Add `wire` as a valid value for `cli_timestamp_format`." +} From a230d85e30210bf7731d4b0721f0761f2ef644ed Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 10:27:12 -0400 Subject: [PATCH 19/40] Update from feedback --- .changes/next-release/enhancement-timestamps-52310.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/enhancement-timestamps-52310.json b/.changes/next-release/enhancement-timestamps-52310.json index eeb8c3350e93..008b443614e0 100644 --- a/.changes/next-release/enhancement-timestamps-52310.json +++ b/.changes/next-release/enhancement-timestamps-52310.json @@ -1,5 +1,5 @@ { "type": "enhancement", "category": "timestamps", - "description": "Add `wire` as a valid value for `cli_timestamp_format`." + "description": "Add ``wire`` as a valid value for ``cli_timestamp_format``." } From 78e4a64f5e4be4cb56a2c8ae5c93a433e9618b23 Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 17 Oct 2025 13:48:33 -0400 Subject: [PATCH 20/40] Upgrade scalarparse debug warning to only print if the user did not configure cli_timestamp_format. --- awscli/customizations/scalarparse.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index ba90b0667085..c5217a0db804 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -51,10 +51,18 @@ def iso_format(value): def add_timestamp_parser(session, v2_debug): factory = session.get_component('response_parser_factory') + print_v2_debug_warnings = v2_debug try: timestamp_format = session.get_scoped_config().get( 'cli_timestamp_format', - 'wire') + None) + if timestamp_format is not None: + # We do not want to print v2 debug warnings if the user explicitly + # configured the cli_timestamp_format, they would not be + # broken in that case. + print_v2_debug_warnings = False + else: + timestamp_format = 'wire' except ProfileNotFound: # If a --profile is provided that does not exist, loading # a value from get_scoped_config will crash the CLI. @@ -91,7 +99,8 @@ def identity_with_warning(x): ) return identity(x) - timestamp_parser = identity_with_warning if v2_debug else identity + timestamp_parser = identity_with_warning \ + if print_v2_debug_warnings else identity elif timestamp_format == 'iso8601': timestamp_parser = iso_format else: From 34ae9f07973b980046f085b7f6f1623135ce8478 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 22 Oct 2025 10:27:14 -0400 Subject: [PATCH 21/40] Update binary-params runtime check so that it no longer depends on starting with --- awscli/clidriver.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 8f8a897df6ea..7e9fa32c3266 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -692,18 +692,17 @@ def _detect_binary_file_migration_change( arg.py_name for arg in arg_table.values() if arg.py_name in parsed_args_to_check and arg.argument_model.type_name == 'blob' - and parsed_args_to_check[arg.py_name].startswith('file://') ] if arg_values_to_check: uni_print( 'AWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' - 'parameter starting with `file://`, AWS CLI v2 will assume ' - 'the content of the file is already base64-encoded. To ' - 'maintain v1 behavior after upgrading to v2, set the ' - '`cli_binary_format` configuration variable to ' - '`raw-in-base64-out`. See https://docs.aws.amazon.com/cli/' - 'latest/userguide/cliv2-migration-changes.html#' - 'cliv2-migration-binaryparam.\n', + 'parameter, AWS CLI v2 will assume the parameter value is ' + 'base64-encoded. To maintain v1 behavior after upgrading ' + 'to v2, set the `cli_binary_format` configuration ' + 'variable to `raw-in-base64-out`. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide' + '/cliv2-migration-changes.html' + '#cliv2-migration-binaryparam.\n', out_file=sys.stderr ) From 8e6b1274aa18779a8021e1ef355386b487a1e84b Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 22 Oct 2025 11:01:03 -0400 Subject: [PATCH 22/40] Keep uni_print in customizations. --- awscli/clidriver.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 7e9fa32c3266..07d12131af68 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -41,7 +41,6 @@ ) from awscli.commands import CLICommand from awscli.compat import get_stderr_text_writer -from awscli.customizations.utils import uni_print from awscli.formatter import get_formatter from awscli.help import ( OperationHelpCommand, @@ -694,7 +693,7 @@ def _detect_binary_file_migration_change( and arg.argument_model.type_name == 'blob' ] if arg_values_to_check: - uni_print( + print( 'AWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' 'parameter, AWS CLI v2 will assume the parameter value is ' 'base64-encoded. To maintain v1 behavior after upgrading ' @@ -703,7 +702,7 @@ def _detect_binary_file_migration_change( 'https://docs.aws.amazon.com/cli/latest/userguide' '/cliv2-migration-changes.html' '#cliv2-migration-binaryparam.\n', - out_file=sys.stderr + file=sys.stderr ) From b502eadeca20825b01b0ddf3e54e4cf47e86b5d8 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 22 Oct 2025 16:07:40 -0400 Subject: [PATCH 23/40] Remove changelog for already-released change. --- .changes/next-release/enhancement-Migration-48043.json | 2 +- .changes/next-release/enhancement-timestamps-52310.json | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changes/next-release/enhancement-timestamps-52310.json diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json index 562578687c30..ad42ffc841a3 100644 --- a/.changes/next-release/enhancement-Migration-48043.json +++ b/.changes/next-release/enhancement-Migration-48043.json @@ -1,5 +1,5 @@ { "type": "enhancement", "category": "Migration", - "description": "Implement a ``--migrate-v2`` flag that detects breaking changes for AWS CLI v2 for entered commands." + "description": "Implement a ``--v2-debug`` flag that detects breaking changes for AWS CLI v2 for entered commands." } diff --git a/.changes/next-release/enhancement-timestamps-52310.json b/.changes/next-release/enhancement-timestamps-52310.json deleted file mode 100644 index 008b443614e0..000000000000 --- a/.changes/next-release/enhancement-timestamps-52310.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "enhancement", - "category": "timestamps", - "description": "Add ``wire`` as a valid value for ``cli_timestamp_format``." -} From 1657364f2a566ec899848eb28f670d69df353743 Mon Sep 17 00:00:00 2001 From: Ahmed Moustafa <35640105+aemous@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:23:07 -0400 Subject: [PATCH 24/40] Revert local changes to requirements.txt made for testing --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2d1dc01ec558..1802a0285af9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ # botocore and the awscli packages are typically developed # in tandem, so we're requiring the latest develop # branch of botocore and s3transfer when working on the awscli. -# -e git+https://github.com/boto/botocore.git@develop#egg=botocore --e file:///Users/aemous/GitHub/botocore +-e git+https://github.com/boto/botocore.git@develop#egg=botocore -e git+https://github.com/boto/s3transfer.git@develop#egg=s3transfer From 53d972f6eac2f6424e9d90e5d1f39338570d114e Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 3 Dec 2025 19:07:25 -0500 Subject: [PATCH 25/40] Update changelog entry based on feedback. --- .changes/next-release/enhancement-Migration-48043.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json index ad42ffc841a3..d52831694a1a 100644 --- a/.changes/next-release/enhancement-Migration-48043.json +++ b/.changes/next-release/enhancement-Migration-48043.json @@ -1,5 +1,5 @@ { "type": "enhancement", "category": "Migration", - "description": "Implement a ``--v2-debug`` flag that detects breaking changes for AWS CLI v2 for entered commands." + "description": "Implement a ``--v2-debug`` flag and ``AWS_CLI_UPGRADE_DEBUG_MODE`` environment variable that detects breaking changes for AWS CLI v2 for entered commands." } From b9700c6d42bb451ea475c807c3008145d3ca150d Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 3 Dec 2025 19:09:13 -0500 Subject: [PATCH 26/40] Fix bug with cli_follow_urlparam. --- awscli/customizations/globalargs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 59582ab61768..c2eb2242f998 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -175,7 +175,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): 'cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n', out_file=sys.stderr ) - if url_params and session.get_scoped_config().get('cli_pager', None): + if url_params and session.get_scoped_config().get('cli_follow_urlparam', True): uni_print( 'AWS CLI v2 UPGRADE WARNING: For input parameters that have ' 'a prefix of http:// or https://, AWS CLI v2 will no longer ' From af65f6b0286f4a57af3045ef67e1552364123794 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 3 Dec 2025 19:45:14 -0500 Subject: [PATCH 27/40] Fix bug with us-east-1 S3 global endpoint breaking change for modeled commands. --- awscli/customizations/globalargs.py | 15 ++++-- tests/unit/customizations/test_globalargs.py | 57 +++++++++++++++++++- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index c2eb2242f998..969bfc38865e 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -135,16 +135,21 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): out_file=sys.stderr ) if ( - s3_config is not None and s3_config - .get( - 'us_east_1_regional_endpoint', - 'legacy' - ) == 'legacy' and region in ('us-east-1', None) + s3_config is None or ( + s3_config is not None and s3_config.get( + 'us_east_1_regional_endpoint', + 'legacy' + ) == 'legacy' and region in ('us-east-1', None) + ) ): session.register( 'request-created.s3.*', warn_if_east_configured_global_endpoint ) + session.register( + 'request-created.s3api.*', + warn_if_east_configured_global_endpoint + ) if session.get_config_variable('api_versions'): uni_print( 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index 4ff130b9d1f7..bc4a09e09a92 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -10,7 +10,7 @@ # 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 unittest.mock import patch +from unittest.mock import patch, Mock from botocore.session import get_session from botocore import UNSIGNED @@ -304,3 +304,58 @@ def test_v2_debug_s3_sigv2(self): 'v4 to authenticate Amazon S3 requests.', output.stderr.getvalue() ) + + def test_v2_debug_s3_us_east_1(self): + parsed_args = FakeParsedArgs(v2_debug=True, region='us-east-1') + session = get_session() + globalargs.detect_migration_breakage(parsed_args, [], session) + def mock_get(key: str): + if key == 'retries': + return {'invocation-id': '012345'} + return None + + with capture_output() as output: + mock_request = Mock() + mock_request.url = 'https://s3.amazonaws.com' + mock_request.context.get.side_effect = mock_get + mock_request.headers = {} + + session.emit( + 'request-created.s3.ListBuckets', + request=mock_request, + operation_name='ListBuckets', + ) + self.assertIn( + 'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' + 'to use the `us-east-1` region, it uses the true regional ' + 'endpoint rather than the global endpoint.', + output.stderr.getvalue() + ) + + def test_v2_debug_s3api_us_east_1(self): + parsed_args = FakeParsedArgs(v2_debug=True, region='us-east-1') + session = get_session() + globalargs.detect_migration_breakage(parsed_args, [], session) + + def mock_get(key: str): + if key == 'retries': + return {'invocation-id': '012345'} + return None + + with capture_output() as output: + mock_request = Mock() + mock_request.url = 'https://s3.amazonaws.com' + mock_request.context.get.side_effect = mock_get + mock_request.headers = {} + + session.emit( + 'request-created.s3api.ListBuckets', + request=mock_request, + operation_name='ListBuckets', + ) + self.assertIn( + 'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' + 'to use the `us-east-1` region, it uses the true regional ' + 'endpoint rather than the global endpoint.', + output.stderr.getvalue() + ) \ No newline at end of file From 4ab5fb86eb4a3ffb7a49f5673d891e936646e965 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 3 Dec 2025 19:53:53 -0500 Subject: [PATCH 28/40] Remove no-v2-debug flag. --- awscli/data/cli.json | 5 ----- awscli/examples/global_options.rst | 4 ---- awscli/examples/global_synopsis.rst | 1 - awscli/utils.py | 2 -- tests/unit/customizations/test_paginate.py | 1 - tests/unit/test_utils.py | 13 ++++--------- 6 files changed, 4 insertions(+), 22 deletions(-) diff --git a/awscli/data/cli.json b/awscli/data/cli.json index d75489e88664..25687399d05c 100644 --- a/awscli/data/cli.json +++ b/awscli/data/cli.json @@ -69,11 +69,6 @@ "action": "store_true", "dest": "v2_debug", "help": "

Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected.

" - }, - "no-v2-debug": { - "action": "store_true", - "dest": "no_v2_debug", - "help": "

Disable AWS CLI v2 migration assistance.

" } } } diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index 914d070ed329..d450cf0ce78a 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -74,7 +74,3 @@ Enable AWS CLI v2 migration assistance. Prints warnings if the command would face a breaking change after swapping AWS CLI v1 for AWS CLI v2 in the current environment. Prints one warning for each breaking change detected. -``--no-v2-debug`` (boolean) - - Disable AWS CLI v2 migration assistance. - diff --git a/awscli/examples/global_synopsis.rst b/awscli/examples/global_synopsis.rst index 1d8295f550ed..12865958a809 100644 --- a/awscli/examples/global_synopsis.rst +++ b/awscli/examples/global_synopsis.rst @@ -13,4 +13,3 @@ [--cli-read-timeout ] [--cli-connect-timeout ] [--v2-debug] -[--no-v2-debug] diff --git a/awscli/utils.py b/awscli/utils.py index 4e4809a5f166..b8e5782418b4 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -137,8 +137,6 @@ def find_service_and_method_in_event_name(event_name): def resolve_v2_debug_mode(args): # Resolve whether v2-debug mode is enabled, # following the correct precedence order. - if no_v2_debug := getattr(args, 'no_v2_debug', False): - return False if getattr(args, 'v2_debug', False): return True if os.environ.get('AWS_CLI_UPGRADE_DEBUG_MODE', '').lower() == 'true': diff --git a/tests/unit/customizations/test_paginate.py b/tests/unit/customizations/test_paginate.py index 2b0a1ed818d4..220f4feb8499 100644 --- a/tests/unit/customizations/test_paginate.py +++ b/tests/unit/customizations/test_paginate.py @@ -335,7 +335,6 @@ def test_v2_debug_call_parameters(self): # migration warning should be printed. # From setUp(), the limit_key is 'Bar' input_tokens = ['Foo', 'Bar'] - self.parsed_globals.no_v2_debug = False self.parsed_globals.v2_debug = True self.parsed_globals.paginate = True self.session.emit_first_non_none_response.side_effect = partial( diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 3f979d8fd25d..5d8f2710094d 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -136,27 +136,22 @@ def test_returns_none_if_event_is_too_short(self): class TestV2DebugResolution(unittest.TestCase): - def test_no_v2_debug_flag_takes_precedence(self): - args = mock.Mock(no_v2_debug=True, v2_debug=True) - with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'}): - self.assertFalse(resolve_v2_debug_mode(args)) - def test_v2_debug_flag_enabled(self): - args = mock.Mock(no_v2_debug=False, v2_debug=True) + args = mock.Mock(v2_debug=True) self.assertTrue(resolve_v2_debug_mode(args)) def test_env_var_enabled(self): - args = mock.Mock(no_v2_debug=False, v2_debug=False) + args = mock.Mock(v2_debug=False) with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'}): self.assertTrue(resolve_v2_debug_mode(args)) def test_all_disabled(self): - args = mock.Mock(no_v2_debug=False, v2_debug=False) + args = mock.Mock(v2_debug=False) with mock.patch.dict(os.environ, {}, clear=True): self.assertFalse(resolve_v2_debug_mode(args)) def test_env_var_non_true_value(self): - args = mock.Mock(no_v2_debug=False, v2_debug=False) + args = mock.Mock(v2_debug=False) with mock.patch.dict(os.environ, {'AWS_CLI_UPGRADE_DEBUG_MODE': 'false'}): self.assertFalse(resolve_v2_debug_mode(args)) From 5a98bdfbeb23028104fc6b6e394918c746d055e9 Mon Sep 17 00:00:00 2001 From: aemous Date: Fri, 5 Dec 2025 14:17:37 -0500 Subject: [PATCH 29/40] Add missing URL to sigv2 S3 warning. --- awscli/customizations/globalargs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 969bfc38865e..3af8e2c482f5 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -252,7 +252,8 @@ def warn_if_sigv2( 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' 'v4 to authenticate Amazon S3 requests. To migrate to AWS CLI ' 'v2 behavior, configure the Signature Version S3 setting to ' - 'version 4.\n', + 'version 4. See https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-sigv4\n', out_file=sys.stderr ) From 9e83e02c366186209dd566d8bbef10d53563eaee Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 8 Dec 2025 11:00:27 -0500 Subject: [PATCH 30/40] Reduced false positive upgrade warnings for URL parameters. --- awscli/argprocess.py | 3 ++- awscli/clidriver.py | 10 +++++----- awscli/customizations/globalargs.py | 15 --------------- awscli/paramfile.py | 23 +++++++++++++++++++---- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/awscli/argprocess.py b/awscli/argprocess.py index 14bc648e3edd..af59b08adb37 100644 --- a/awscli/argprocess.py +++ b/awscli/argprocess.py @@ -65,7 +65,7 @@ class TooComplexError(Exception): def unpack_argument( - session, service_name, operation_name, cli_argument, value + session, service_name, operation_name, cli_argument, value, parsed_globals ): """ Unpack an argument's value from the commandline. This is part one of a two @@ -83,6 +83,7 @@ def unpack_argument( value=value, service_name=service_name, operation_name=operation_name, + parsed_globals=parsed_globals, ) if value_override is not None: diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 07d12131af68..83c83bae23e9 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -542,7 +542,7 @@ def __call__(self, args, parsed_globals): event, parsed_args=parsed_args, parsed_globals=parsed_globals ) call_parameters = self._build_call_parameters( - parsed_args, self.arg_table + parsed_args, self.arg_table, parsed_globals ) self._detect_binary_file_migration_change( @@ -596,7 +596,7 @@ def _add_help(self, parser): # CLIArguments for values. parser.add_argument('help', nargs='?') - def _build_call_parameters(self, args, arg_table): + def _build_call_parameters(self, args, arg_table, parsed_globals): # We need to convert the args specified on the command # line as valid **kwargs we can hand to botocore. service_params = {} @@ -607,11 +607,11 @@ def _build_call_parameters(self, args, arg_table): py_name = arg_object.py_name if py_name in parsed_args: value = parsed_args[py_name] - value = self._unpack_arg(arg_object, value) + value = self._unpack_arg(arg_object, value, parsed_globals) arg_object.add_to_params(service_params, value) return service_params - def _unpack_arg(self, cli_argument, value): + def _unpack_arg(self, cli_argument, value, parsed_globals): # Unpacks a commandline argument into a Python value by firing the # load-cli-arg.service-name.operation-name event. session = self._session @@ -619,7 +619,7 @@ def _unpack_arg(self, cli_argument, value): operation_name = xform_name(self._name, '-') return unpack_argument( - session, service_name, operation_name, cli_argument, value + session, service_name, operation_name, cli_argument, value, parsed_globals ) def _create_argument_table(self): diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 3af8e2c482f5..281c707a49b4 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -101,10 +101,6 @@ def resolve_cli_connect_timeout(parsed_args, session, **kwargs): def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): if not resolve_v2_debug_mode(parsed_args): return - url_params = [ - param for param in remaining_args - if param.startswith('http://') or param.startswith('https://') - ] region = parsed_args.region or session.get_config_variable('region') s3_config = session.get_config_variable('s3') if ( @@ -180,17 +176,6 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): 'cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n', out_file=sys.stderr ) - if url_params and session.get_scoped_config().get('cli_follow_urlparam', True): - uni_print( - 'AWS CLI v2 UPGRADE WARNING: For input parameters that have ' - 'a prefix of http:// or https://, AWS CLI v2 will no longer ' - 'automatically request the content of the URL for the ' - 'parameter, and the `cli_follow_urlparam` option has been ' - 'removed. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' - '#cliv2-migration-paramfile.\n', - out_file=sys.stderr - ) for working, obsolete in HIDDEN_ALIASES.items(): working_split = working.split('.') working_service = working_split[0] diff --git a/awscli/paramfile.py b/awscli/paramfile.py index 14cd7fc49330..c07d67c21247 100644 --- a/awscli/paramfile.py +++ b/awscli/paramfile.py @@ -10,6 +10,8 @@ # 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. +import sys + import copy import logging import os @@ -20,6 +22,7 @@ from awscli import argprocess from awscli.compat import compat_open +from awscli.utils import resolve_v2_debug_mode logger = logging.getLogger(__name__) @@ -166,7 +169,7 @@ def __init__(self, prefixes=None): prefixes.update(REMOTE_PREFIX_MAP) self._prefixes = prefixes - def __call__(self, event_name, param, value, **kwargs): + def __call__(self, event_name, param, value, parsed_globals, **kwargs): """Handler that supports param values from URIs.""" cli_argument = param qualified_param_name = '.'.join(event_name.split('.')[1:]) @@ -175,13 +178,25 @@ def __call__(self, event_name, param, value, **kwargs): ): return else: - return self._check_for_uri_param(cli_argument, value) + return self._check_for_uri_param(cli_argument, value, parsed_globals) - def _check_for_uri_param(self, param, value): + def _check_for_uri_param(self, param, value, parsed_globals): if isinstance(value, list) and len(value) == 1: value = value[0] try: - return get_paramfile(value, self._prefixes) + param_file = get_paramfile(value, self._prefixes) + if param_file is not None and resolve_v2_debug_mode(parsed_globals): + print( + 'AWS CLI v2 UPGRADE WARNING: For input parameters that ' + 'have a prefix of http:// or https://, AWS CLI v2 will ' + 'not automatically request the content of the URL for the ' + 'parameter, and the `cli_follow_urlparam` option has been ' + 'removed. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-paramfile.\n', + file=sys.stderr, + ) + return param_file except ResourceLoadingError as e: raise argprocess.ParamError(param.cli_name, str(e)) From 46eee3c96ecddec7475ee3ee83f975adef1e5584 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 8 Dec 2025 11:07:53 -0500 Subject: [PATCH 31/40] Add more spacing around upgrade warnings. --- awscli/clidriver.py | 2 +- awscli/customizations/cloudformation/deploy.py | 2 +- awscli/customizations/globalargs.py | 16 ++++++++-------- awscli/customizations/paginate.py | 2 +- awscli/customizations/s3/subcommands.py | 2 +- awscli/customizations/scalarparse.py | 2 +- awscli/paramfile.py | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 83c83bae23e9..495f01aa2b14 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -694,7 +694,7 @@ def _detect_binary_file_migration_change( ] if arg_values_to_check: print( - 'AWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' + '\nAWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' 'parameter, AWS CLI v2 will assume the parameter value is ' 'base64-encoded. To maintain v1 behavior after upgrading ' 'to v2, set the `cli_binary_format` configuration ' diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index e33b03a8f362..c305cc3d2380 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -333,7 +333,7 @@ def deploy(self, deployer, stack_name, template_str, try: if v2_debug and fail_on_empty_changeset: uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' 'deploying an AWS CloudFormation Template that ' 'results in an empty changeset will NOT result in an ' 'error. You can add the -–no-fail-on-empty-changeset ' diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 281c707a49b4..156bc29cb782 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -108,7 +108,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): == '' and 'AWS_PAGER' not in os.environ ): uni_print( - 'AWS CLI v2 UPGRADE WARNING: By default, the AWS CLI version 2 ' + '\nAWS CLI v2 UPGRADE WARNING: By default, the AWS CLI version 2 ' 'returns all output through your operating system’s default pager ' 'program. To retain AWS CLI v1 behavior after upgrading to AWS ' 'CLI v2, set the `cli_pager` configuration setting, or the ' @@ -120,7 +120,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ: if 'AWS_CLI_FILE_ENCODING' not in os.environ: uni_print( - 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' + '\nAWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' 'PYTHONIOENCODING environment variables are unsupported ' 'in AWS CLI v2. AWS CLI v2 uses the `AWS_CLI_FILE_ENCODING` ' 'variable instead; set this environment variable to retain ' @@ -148,7 +148,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if session.get_config_variable('api_versions'): uni_print( - 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' + '\nAWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' 'calling earlier versions of AWS service APIs via the ' '`api_versions` configuration file setting. To migrate to v2 ' 'behavior, remove the `api_versions` configuration setting, and ' @@ -159,7 +159,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if session.full_config.get('plugins', {}): uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugin support ' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugin support ' 'is provisional. If you rely on plugins, be sure to lock into ' 'a particular version of the AWS CLI and test the ' 'functionality of your plugins for each upgrade. See ' @@ -170,7 +170,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': uni_print( - 'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has ' + '\nAWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has ' 'been removed in AWS CLI v2. You must use `ecr get-login-password` ' 'instead. See https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html#cliv2-migration-ecr-get-login.\n', @@ -187,7 +187,7 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): and f"--{working_param}" in remaining_args ): uni_print( - 'AWS CLI v2 UPGRADE WARNING: You have entered command ' + '\nAWS CLI v2 UPGRADE WARNING: You have entered command ' 'arguments that uses at least 1 of 21 hidden aliases that ' 'were removed in AWS CLI v2. You must replace usage of the ' 'obsolete alias with the corresponding working parameter. See ' @@ -215,7 +215,7 @@ def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): # from botocore, we check the endpoint URL directly. if 's3.amazonaws.com' in request.url: uni_print( - 'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' + '\nAWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' 'to use the `us-east-1` region, it uses the true regional ' 'endpoint rather than the global endpoint. To retain AWS CLI v1 ' 'behavior after upgrading to AWS CLI v2, configure the `region` ' @@ -234,7 +234,7 @@ def warn_if_sigv2( ): if context.get('auth_type', None) == 'v2': uni_print( - 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' + '\nAWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' 'v4 to authenticate Amazon S3 requests. To migrate to AWS CLI ' 'v2 behavior, configure the Signature Version S3 setting to ' 'version 4. See https://docs.aws.amazon.com/cli/latest/userguide/' diff --git a/awscli/customizations/paginate.py b/awscli/customizations/paginate.py index 2d64bae611c3..86802f21efb6 100644 --- a/awscli/customizations/paginate.py +++ b/awscli/customizations/paginate.py @@ -304,7 +304,7 @@ def check_should_enable_pagination_call_parameters( ] if pagination_params_in_input_tokens: uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' 'turned off. To retain AWS CLI v1 behavior after upgrading to ' diff --git a/awscli/customizations/s3/subcommands.py b/awscli/customizations/s3/subcommands.py index b05ae1857020..7fb8c6047c59 100644 --- a/awscli/customizations/s3/subcommands.py +++ b/awscli/customizations/s3/subcommands.py @@ -1060,7 +1060,7 @@ def run(self): if self.parameters['v2_debug']: if operation_name == 'copy': uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object ' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object ' 'properties will be copied from the source in multipart ' 'copies between S3 buckets. This may result in extra S3 ' 'API calls being made. Breakage may occur if the principal ' diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index c5217a0db804..3211f9f0a2fa 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -88,7 +88,7 @@ def identity_with_warning(x): if not encountered_timestamp: encountered_timestamp = True uni_print( - 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, all timestamp ' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, all timestamp ' 'response values are returned in the ISO 8601 format. To ' 'migrate to v2 behavior, set the configuration variable ' '`cli_timestamp_format` to `iso8601`. See https://' diff --git a/awscli/paramfile.py b/awscli/paramfile.py index c07d67c21247..4ac85193473e 100644 --- a/awscli/paramfile.py +++ b/awscli/paramfile.py @@ -187,7 +187,7 @@ def _check_for_uri_param(self, param, value, parsed_globals): param_file = get_paramfile(value, self._prefixes) if param_file is not None and resolve_v2_debug_mode(parsed_globals): print( - 'AWS CLI v2 UPGRADE WARNING: For input parameters that ' + '\nAWS CLI v2 UPGRADE WARNING: For input parameters that ' 'have a prefix of http:// or https://, AWS CLI v2 will ' 'not automatically request the content of the URL for the ' 'parameter, and the `cli_follow_urlparam` option has been ' From cab30332be5c66c91915fa61da16fdadf4ae2814 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 8 Dec 2025 11:24:00 -0500 Subject: [PATCH 32/40] Update tests based on changes to function interfaces. --- awscli/customizations/commands.py | 3 ++- tests/unit/test_argprocess.py | 6 +++--- tests/unit/test_clidriver.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/awscli/customizations/commands.py b/awscli/customizations/commands.py index 45ac54e565ff..8bec7017731a 100644 --- a/awscli/customizations/commands.py +++ b/awscli/customizations/commands.py @@ -154,7 +154,8 @@ def __call__(self, args, parsed_globals): 'custom', self.name, cli_argument, - value + value, + parsed_globals ) # If this parameter has a schema defined, then allow plugins diff --git a/tests/unit/test_argprocess.py b/tests/unit/test_argprocess.py index e59eb6c3b64c..e37398d82145 100644 --- a/tests/unit/test_argprocess.py +++ b/tests/unit/test_argprocess.py @@ -80,7 +80,7 @@ def test_uri_param(self): ) f.write(json_argument) f.flush() - result = self.uri_param('event-name', p, 'file://%s' % f.name) + result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock()) self.assertEqual(result, json_argument) def test_uri_param_no_paramfile_false(self): @@ -90,7 +90,7 @@ def test_uri_param_no_paramfile_false(self): json_argument = json.dumps([{"Name": "instance-id", "Values": ["i-1234"]}]) f.write(json_argument) f.flush() - result = self.uri_param('event-name', p, 'file://%s' % f.name) + result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock()) self.assertEqual(result, json_argument) def test_uri_param_no_paramfile_true(self): @@ -100,7 +100,7 @@ def test_uri_param_no_paramfile_true(self): json_argument = json.dumps([{"Name": "instance-id", "Values": ["i-1234"]}]) f.write(json_argument) f.flush() - result = self.uri_param('event-name', p, 'file://%s' % f.name) + result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock()) self.assertEqual(result, None) diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py index bfc17cf71ab8..b70920e2c2f9 100644 --- a/tests/unit/test_clidriver.py +++ b/tests/unit/test_clidriver.py @@ -543,6 +543,7 @@ def test_custom_arg_paramfile(self, mock_handler): param=mock.ANY, service_name='ec2', value='file:///foo', + parsed_globals=mock.ANY, ) # Make sure it was called with our passed-in URI self.assertEqual( @@ -572,6 +573,7 @@ def test_custom_command_paramfile(self, mock_handler): param=mock.ANY, service_name='custom', value='file:///foo', + parsed_globals=mock.ANY, ) def test_custom_arg_no_paramfile(self): From 5972faebcea7159f9b8e28ff0cdf56f4ff7e6d13 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 9 Dec 2025 19:14:42 -0500 Subject: [PATCH 33/40] Update wordings for each warning. --- awscli/clidriver.py | 14 +-- .../customizations/cloudformation/deploy.py | 17 ++-- awscli/customizations/globalargs.py | 88 +++++++++++-------- awscli/customizations/paginate.py | 4 +- awscli/customizations/s3/subcommands.py | 13 +-- awscli/customizations/scalarparse.py | 13 +-- awscli/paramfile.py | 12 +-- 7 files changed, 93 insertions(+), 68 deletions(-) diff --git a/awscli/clidriver.py b/awscli/clidriver.py index 495f01aa2b14..81429090fdc8 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -694,13 +694,15 @@ def _detect_binary_file_migration_change( ] if arg_values_to_check: print( - '\nAWS CLI v2 UPGRADE WARNING: When specifying a blob-type ' - 'parameter, AWS CLI v2 will assume the parameter value is ' - 'base64-encoded. To maintain v1 behavior after upgrading ' - 'to v2, set the `cli_binary_format` configuration ' + '\nAWS CLI v2 UPGRADE WARNING: When specifying a ' + 'blob-type parameter, AWS CLI v2 will assume the ' + 'parameter value is base64-encoded. This is different ' + 'from v1 behavior, where the AWS CLI will automatically ' + 'encode the value to base64. To retain v1 behavior in ' + 'AWS CLI v2, set the `cli_binary_format` configuration ' 'variable to `raw-in-base64-out`. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide' - '/cliv2-migration-changes.html' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' '#cliv2-migration-binaryparam.\n', file=sys.stderr ) diff --git a/awscli/customizations/cloudformation/deploy.py b/awscli/customizations/cloudformation/deploy.py index c305cc3d2380..08a0a5647ae7 100644 --- a/awscli/customizations/cloudformation/deploy.py +++ b/awscli/customizations/cloudformation/deploy.py @@ -333,14 +333,15 @@ def deploy(self, deployer, stack_name, template_str, try: if v2_debug and fail_on_empty_changeset: uni_print( - '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, ' - 'deploying an AWS CloudFormation Template that ' - 'results in an empty changeset will NOT result in an ' - 'error. You can add the -–no-fail-on-empty-changeset ' - 'flag to migrate to v2 behavior and resolve this ' - 'warning. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' - '#cliv2-migration-cfn.\n', + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, deploying ' + 'an AWS CloudFormation Template that results in an empty ' + 'changeset will NOT result in an error by default. This ' + 'is different from v1 behavior, where empty changesets ' + 'result in an error by default. To migrate to v2 behavior ' + 'and resolve this warning, you can add the ' + '`--no-fail-on-empty-changeset` flag to the command. ' + 'See https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-cfn.\n', out_file=sys.stderr ) result = deployer.create_and_wait_for_changeset( diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 156bc29cb782..05ac1272bbb8 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -108,24 +108,26 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): == '' and 'AWS_PAGER' not in os.environ ): uni_print( - '\nAWS CLI v2 UPGRADE WARNING: By default, the AWS CLI version 2 ' - 'returns all output through your operating system’s default pager ' - 'program. To retain AWS CLI v1 behavior after upgrading to AWS ' - 'CLI v2, set the `cli_pager` configuration setting, or the ' + '\nAWS CLI v2 UPGRADE WARNING: By default, the AWS CLI v2 returns ' + 'all output through your operating system’s default pager ' + 'program. This is different from v1 behavior, where the system ' + 'pager is not used by default. To retain AWS CLI v1 behavior in ' + 'AWS CLI v2, set the `cli_pager` configuration setting, or the ' '`AWS_PAGER` environment variable, to the empty string. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide' - '/cliv2-migration-changes.html#cliv2-migration-output-pager.\n', + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-output-pager.\n', out_file=sys.stderr ) if 'PYTHONUTF8' in os.environ or 'PYTHONIOENCODING' in os.environ: if 'AWS_CLI_FILE_ENCODING' not in os.environ: uni_print( - '\nAWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' - 'PYTHONIOENCODING environment variables are unsupported ' - 'in AWS CLI v2. AWS CLI v2 uses the `AWS_CLI_FILE_ENCODING` ' - 'variable instead; set this environment variable to retain ' - 'AWS CLI v1 behavior after upgrading to AWS CLI v2. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide/' + '\nThe AWS CLI v2 does not support The `PYTHONUTF8` and ' + '`PYTHONIOENCODING` environment variables, and instead uses ' + 'the `AWS_CLI_FILE_ENCODING` variable. This is different from ' + 'v1 behavior, where the former two variables are used ' + 'instead. To retain AWS CLI v1 behavior in AWS CLI v2, set ' + 'the `AWS_CLI_FILE_ENCODING` environment variable instead. ' + 'See https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' '#cliv2-migration-encodingenvvar.\n', out_file=sys.stderr @@ -148,24 +150,28 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ) if session.get_config_variable('api_versions'): uni_print( - '\nAWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' - 'calling earlier versions of AWS service APIs via the ' - '`api_versions` configuration file setting. To migrate to v2 ' + '\nAWS CLI v2 UPGRADE WARNING: AWS CLI v2 UPGRADE WARNING: ' + 'The AWS CLI v2 does not support calling older versions of AWS ' + 'service APIs via the `api_versions` configuration file setting. This ' + 'is different from v1 behavior, where this configuration setting ' + 'can be used to pin older API versions. To migrate to v2 ' 'behavior, remove the `api_versions` configuration setting, and ' - 'test against the latest API versions. See ' + 'test against the latest service API versions. See ' 'https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html#cliv2-migration-api-versions.\n', out_file = sys.stderr ) if session.full_config.get('plugins', {}): uni_print( - '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugin support ' - 'is provisional. If you rely on plugins, be sure to lock into ' - 'a particular version of the AWS CLI and test the ' - 'functionality of your plugins for each upgrade. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide/' - 'cliv2-migration-changes.html#' - 'cliv2-migration-profile-plugins\n', + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, plugins are ' + 'disabled by default, and support for plugins is provisional. ' + 'This is different from v1 behavior, where plugin support is URL ' + 'below to update your configuration to enable plugins in AWS CLI ' + 'v2. Also, be sure to lock into a particular version of the AWS ' + 'CLI and test the functionality of your plugins every time AWS ' + 'CLI v2 is upgraded. See https://docs.aws.amazon.com/cli/latest/' + 'userguide/cliv2-migration-changes.html' + '#cliv2-migration-profile-plugins.\n', out_file=sys.stderr ) if parsed_args.command == 'ecr' and remaining_args[0] == 'get-login': @@ -188,11 +194,12 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): ): uni_print( '\nAWS CLI v2 UPGRADE WARNING: You have entered command ' - 'arguments that uses at least 1 of 21 hidden aliases that ' - 'were removed in AWS CLI v2. You must replace usage of the ' - 'obsolete alias with the corresponding working parameter. See ' - 'https://docs.aws.amazon.com/cli/latest/userguide' - '/cliv2-migration-changes.html#cliv2-migration-aliases.\n', + 'arguments that use at least 1 of 21 built-in ("hidden") ' + 'aliases that were removed in AWS CLI v2. For this command ' + 'to work in AWS CLI v2, you must replace usage of the alias ' + 'with the corresponding parameter in AWS CLI v2. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-aliases.\n', out_file=sys.stderr ) # Register against the provide-client-params event to ensure that the @@ -215,12 +222,14 @@ def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): # from botocore, we check the endpoint URL directly. if 's3.amazonaws.com' in request.url: uni_print( - '\nAWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 ' - 'to use the `us-east-1` region, it uses the true regional ' - 'endpoint rather than the global endpoint. To retain AWS CLI v1 ' - 'behavior after upgrading to AWS CLI v2, configure the `region` ' - 'setting to `aws-global`. See https://docs.aws.amazon.com/cli' - '/latest/userguide/cliv2-migration-changes.html' + '\nAWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 to ' + 'use the `us-east-1` region, it uses the true regional endpoint ' + 'rather than the global endpoint. This is different from v1 ' + 'behavior, where the global endpoint would be used when the ' + 'region is `us-east-1`. To retain AWS CLI v1 behavior in AWS ' + 'CLI v2, configure the region setting to `aws-global`. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' '#cliv2-migration-s3-regional-endpoint.\n', out_file=sys.stderr ) @@ -235,10 +244,13 @@ def warn_if_sigv2( if context.get('auth_type', None) == 'v2': uni_print( '\nAWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature ' - 'v4 to authenticate Amazon S3 requests. To migrate to AWS CLI ' - 'v2 behavior, configure the Signature Version S3 setting to ' - 'version 4. See https://docs.aws.amazon.com/cli/latest/userguide/' - 'cliv2-migration-changes.html#cliv2-migration-sigv4\n', + 'v4 to authenticate Amazon S3 requests. This is different from ' + 'v1 behavior, where the signature used for Amazon S3 requests may ' + 'vary depending on configuration settings, region, and the ' + 'bucket being used. To migrate to AWS CLI v2 behavior, configure ' + 'the Signature Version S3 setting to version 4. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html#cliv2-migration-sigv4.\n', out_file=sys.stderr ) diff --git a/awscli/customizations/paginate.py b/awscli/customizations/paginate.py index 86802f21efb6..3bae390d7b18 100644 --- a/awscli/customizations/paginate.py +++ b/awscli/customizations/paginate.py @@ -307,7 +307,9 @@ def check_should_enable_pagination_call_parameters( '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify ' 'pagination parameters by using a file with the ' '`--cli-input-json` parameter, automatic pagination will be ' - 'turned off. To retain AWS CLI v1 behavior after upgrading to ' + 'turned off. This is different from v1 behavior, where ' + 'pagination parameters specified via the `--cli-input-json` ' + 'parameter are ignored. To retain AWS CLI v1 behavior in ' 'AWS CLI v2, remove all pagination parameters from the input ' 'JSON. See https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' diff --git a/awscli/customizations/s3/subcommands.py b/awscli/customizations/s3/subcommands.py index 7fb8c6047c59..47ef10e88eec 100644 --- a/awscli/customizations/s3/subcommands.py +++ b/awscli/customizations/s3/subcommands.py @@ -1062,13 +1062,16 @@ def run(self): uni_print( '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object ' 'properties will be copied from the source in multipart ' - 'copies between S3 buckets. This may result in extra S3 ' - 'API calls being made. Breakage may occur if the principal ' - 'does not have permission to call these extra APIs. This ' - 'warning cannot be resolved. See ' + 'copies between S3 buckets initiated via `aws s3` ' + 'commands, resulting in additional S3 API calls to ' + 'transfer the metadata. Note that the principal must ' + 'have permission to call these APIs, or the command may ' + 'fail. This is different from v1 behavior, where metadata ' + 'is not copied. For guidance on retaining v1 behavior in ' + 'AWS CLI v2, or for more details, see ' 'https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' - '#cliv2-migration-s3-copy-metadata\n\n', + '#cliv2-migration-s3-copy-metadata.\n\n', out_file=sys.stderr ) diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py index 3211f9f0a2fa..8ddf868b4984 100644 --- a/awscli/customizations/scalarparse.py +++ b/awscli/customizations/scalarparse.py @@ -88,11 +88,14 @@ def identity_with_warning(x): if not encountered_timestamp: encountered_timestamp = True uni_print( - '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, all timestamp ' - 'response values are returned in the ISO 8601 format. To ' - 'migrate to v2 behavior, set the configuration variable ' - '`cli_timestamp_format` to `iso8601`. See https://' - 'docs.aws.amazon.com/cli/latest/userguide/' + '\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, all ' + 'timestamp response values are returned in the ISO 8601 ' + 'format. This is different from v1 behavior, where the ' + 'timestamps are returned as they appear in the service ' + 'API response. To retain AWS CLI v1 behavior in AWS CLI ' + 'v2, set the configuration variable ' + '`cli_timestamp_format` to `wire`. See ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' 'cliv2-migration-changes.html' '#cliv2-migration-timestamp.\n', out_file=sys.stderr diff --git a/awscli/paramfile.py b/awscli/paramfile.py index 4ac85193473e..baaa28f63150 100644 --- a/awscli/paramfile.py +++ b/awscli/paramfile.py @@ -188,11 +188,13 @@ def _check_for_uri_param(self, param, value, parsed_globals): if param_file is not None and resolve_v2_debug_mode(parsed_globals): print( '\nAWS CLI v2 UPGRADE WARNING: For input parameters that ' - 'have a prefix of http:// or https://, AWS CLI v2 will ' - 'not automatically request the content of the URL for the ' - 'parameter, and the `cli_follow_urlparam` option has been ' - 'removed. See https://docs.aws.amazon.com/cli/latest/' - 'userguide/cliv2-migration-changes.html' + 'have a prefix of `http://` or `https://`, AWS CLI v2 ' + 'will not automatically request the content of the URL ' + 'for the parameter, and the `cli_follow_urlparam` option ' + 'has been removed. For guidance on how to adapt this ' + 'command to AWS CLI v2 usage, see ' + 'https://docs.aws.amazon.com/cli/latest/userguide/' + 'cliv2-migration-changes.html' '#cliv2-migration-paramfile.\n', file=sys.stderr, ) From ebc837a54c8fdf5e74f7a067fd15a2483e28b010 Mon Sep 17 00:00:00 2001 From: aemous Date: Tue, 9 Dec 2025 19:37:51 -0500 Subject: [PATCH 34/40] Update tests based on updated wording in warnings. --- tests/functional/test_api_versions.py | 6 +++--- tests/unit/customizations/s3/test_subcommands.py | 9 ++++++--- tests/unit/customizations/test_globalargs.py | 12 ++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/functional/test_api_versions.py b/tests/functional/test_api_versions.py index e98f0159cb44..84748819b421 100644 --- a/tests/functional/test_api_versions.py +++ b/tests/functional/test_api_versions.py @@ -59,9 +59,9 @@ def test_v2_debug_migration_warning(self): # Make sure that the migration warning is printed since the user # specified --v2-debug self.assertIn( - 'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 does not support ' - 'calling earlier versions of AWS service APIs via the ' - '`api_versions` configuration file setting.', + 'AWS CLI v2 UPGRADE WARNING: AWS CLI v2 UPGRADE WARNING: The AWS ' + 'CLI v2 does not support calling older versions of AWS service ' + 'APIs via the `api_versions` configuration file setting.', stderr ) diff --git a/tests/unit/customizations/s3/test_subcommands.py b/tests/unit/customizations/s3/test_subcommands.py index b19d65a352ed..25fc257fcb21 100644 --- a/tests/unit/customizations/s3/test_subcommands.py +++ b/tests/unit/customizations/s3/test_subcommands.py @@ -635,9 +635,12 @@ def test_v2_debug_mv(self): cmd_arc.create_instructions() self.patch_make_request() cmd_arc.run() - warning_str = 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object '\ - 'properties will be copied from the source in multipart '\ - 'copies between S3 buckets.' + warning_str = ( + 'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object ' + 'properties will be copied from the source in ' + 'multipart copies between S3 buckets initiated via ' + '`aws s3` commands' + ) output_str = f"(dryrun) move: {s3_file} to {s3_file}" self.assertIn(warning_str, self.err_output.getvalue()) self.assertIn(output_str, self.output.getvalue()) diff --git a/tests/unit/customizations/test_globalargs.py b/tests/unit/customizations/test_globalargs.py index bc4a09e09a92..d53438f5c095 100644 --- a/tests/unit/customizations/test_globalargs.py +++ b/tests/unit/customizations/test_globalargs.py @@ -253,9 +253,9 @@ def test_v2_debug_python_utf8_env_var(self): with capture_output() as output: globalargs.detect_migration_breakage(parsed_args, [], session) self.assertIn( - 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' - 'PYTHONIOENCODING environment variables are unsupported ' - 'in AWS CLI v2.', + 'The AWS CLI v2 does not support The `PYTHONUTF8` and ' + '`PYTHONIOENCODING` environment variables, and instead ' + 'uses the `AWS_CLI_FILE_ENCODING` variable', output.stderr.getvalue() ) @@ -281,9 +281,9 @@ def test_v2_debug_python_io_encoding_env_var(self): with capture_output() as output: globalargs.detect_migration_breakage(parsed_args, [], session) self.assertIn( - 'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and ' - 'PYTHONIOENCODING environment variables are unsupported ' - 'in AWS CLI v2.', + 'The AWS CLI v2 does not support The `PYTHONUTF8` and ' + '`PYTHONIOENCODING` environment variables, and instead ' + 'uses the `AWS_CLI_FILE_ENCODING` variable', output.stderr.getvalue() ) From d2f3f515244f184b09619b9a825b5a33f1482e00 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 10 Dec 2025 12:01:52 -0500 Subject: [PATCH 35/40] Modify us-east-1 S3 endpoint check to only check the hostname. --- awscli/customizations/globalargs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 05ac1272bbb8..e7d5bdf8c8d9 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -220,7 +220,8 @@ def warn_if_east_configured_global_endpoint(request, operation_name, **kwargs): # The regional us-east-1 endpoint is used in certain cases (e.g. # FIPS/Dual-Stack is enabled). Rather than duplicating this logic # from botocore, we check the endpoint URL directly. - if 's3.amazonaws.com' in request.url: + parsed_url = urlparse.urlparse(request.url) + if parsed_url.hostname.endswith('s3.amazonaws.com'): uni_print( '\nAWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 to ' 'use the `us-east-1` region, it uses the true regional endpoint ' From ba5c0d0dbd0e1ae2a9f5d3e166b68c1d69a68171 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 10 Dec 2025 12:03:09 -0500 Subject: [PATCH 36/40] Switch changelog entry to feature. --- .changes/next-release/enhancement-Migration-48043.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json index d52831694a1a..7e24454e4bdf 100644 --- a/.changes/next-release/enhancement-Migration-48043.json +++ b/.changes/next-release/enhancement-Migration-48043.json @@ -1,5 +1,5 @@ { - "type": "enhancement", + "type": "feature", "category": "Migration", "description": "Implement a ``--v2-debug`` flag and ``AWS_CLI_UPGRADE_DEBUG_MODE`` environment variable that detects breaking changes for AWS CLI v2 for entered commands." } From c1a9fc09fe7c4b939199bba74f4a6c0b65fe5ca8 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 10 Dec 2025 12:15:37 -0500 Subject: [PATCH 37/40] Patch us-east-1 detection logic. --- awscli/customizations/globalargs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index e7d5bdf8c8d9..efc39c0123de 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -133,12 +133,12 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): out_file=sys.stderr ) if ( - s3_config is None or ( - s3_config is not None and s3_config.get( - 'us_east_1_regional_endpoint', - 'legacy' - ) == 'legacy' and region in ('us-east-1', None) - ) + (s3_config is None or ( + s3_config is not None and s3_config.get( + 'us_east_1_regional_endpoint', + 'legacy' + ) == 'legacy')) and region in ('us-east-1', None) + and parsed_args.endpoint_url is None ): session.register( 'request-created.s3.*', From 7365324404339dc5f31b2ff3b188e5eb6345fb14 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 10 Dec 2025 12:18:59 -0500 Subject: [PATCH 38/40] Patch us-east-1 detection based on extra conditions. --- awscli/customizations/globalargs.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index efc39c0123de..0e6533ef578a 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -133,11 +133,12 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): out_file=sys.stderr ) if ( - (s3_config is None or ( - s3_config is not None and s3_config.get( - 'us_east_1_regional_endpoint', - 'legacy' - ) == 'legacy')) and region in ('us-east-1', None) + ( + s3_config is None + or s3_config.get('us_east_1_regional_endpoint', 'legacy') + == 'legacy' + ) + and region in ('us-east-1', None) and parsed_args.endpoint_url is None ): session.register( From 5d99f67033bcb24a53762bb72155a73cc55c9868 Mon Sep 17 00:00:00 2001 From: aemous Date: Wed, 10 Dec 2025 12:20:26 -0500 Subject: [PATCH 39/40] Remake feature changelog entry. --- ...ancement-Migration-48043.json => feature-Migration-29690.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changes/next-release/{enhancement-Migration-48043.json => feature-Migration-29690.json} (100%) diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/feature-Migration-29690.json similarity index 100% rename from .changes/next-release/enhancement-Migration-48043.json rename to .changes/next-release/feature-Migration-29690.json From 49688a6e47ea1c5919d2d39137207f0279166b1a Mon Sep 17 00:00:00 2001 From: Ahmed Moustafa <35640105+aemous@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:37:59 -0500 Subject: [PATCH 40/40] Remove endpoint-url check --- awscli/customizations/globalargs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awscli/customizations/globalargs.py b/awscli/customizations/globalargs.py index 0e6533ef578a..b7255708ce4b 100644 --- a/awscli/customizations/globalargs.py +++ b/awscli/customizations/globalargs.py @@ -139,7 +139,6 @@ def detect_migration_breakage(parsed_args, remaining_args, session, **kwargs): == 'legacy' ) and region in ('us-east-1', None) - and parsed_args.endpoint_url is None ): session.register( 'request-created.s3.*',