diff --git a/.changes/next-release/enhancement-Migration-48043.json b/.changes/next-release/enhancement-Migration-48043.json new file mode 100644 index 000000000000..562578687c30 --- /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/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 e185fecf0ae4..1945dcbb9141 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -79,6 +79,8 @@ 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'), @@ -225,7 +227,7 @@ def main(self, args=None): # 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' @@ -279,8 +281,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) if args.profile: self.session.set_config_variable('profile', args.profile) if args.region: 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/globalargs.py b/awscli/customizations/globalargs.py index 93321ccd85aa..2ef6b9ef6a15 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,20 @@ 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, 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') def resolve_cli_read_timeout(parsed_args, session, **kwargs): arg_name = 'read_timeout' 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..25687399d05c 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.

" + }, + "v2-debug": { + "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.

" } } } diff --git a/awscli/examples/global_options.rst b/awscli/examples/global_options.rst index 2f8c7115e8ae..d450cf0ce78a 100644 --- a/awscli/examples/global_options.rst +++ b/awscli/examples/global_options.rst @@ -70,3 +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. +``--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 c5baaa9583ba..12865958a809 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 ] +[--v2-debug] diff --git a/awscli/utils.py b/awscli/utils.py index 4a3096320fad..3e7e9d6e8e37 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -205,8 +205,13 @@ 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): + session.emit( + 'top-level-args-parsed', + parsed_args=args, + remaining_args=remaining, + session=session + ) def is_a_tty(): 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'], 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": "", } } },