Skip to content

Commit 0cfabe5

Browse files
authored
Implement upgrade debug mode
1 parent 9826036 commit 0cfabe5

26 files changed

+807
-55
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "Migration",
4+
"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."
5+
}

awscli/alias.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def __call__(self, args, parsed_globals):
183183
parsed_alias_args, remaining = self._parser.parse_known_args(
184184
alias_args
185185
)
186-
self._update_parsed_globals(parsed_alias_args, parsed_globals)
186+
self._update_parsed_globals(parsed_alias_args, parsed_globals, remaining)
187187
# Take any of the remaining arguments that were not parsed out and
188188
# prepend them to the remaining args provided to the alias.
189189
remaining.extend(args)
@@ -228,7 +228,7 @@ def _get_alias_args(self):
228228
)
229229
return alias_args
230230

231-
def _update_parsed_globals(self, parsed_alias_args, parsed_globals):
231+
def _update_parsed_globals(self, parsed_alias_args, parsed_globals, remaining):
232232
global_params_to_update = self._get_global_parameters_to_update(
233233
parsed_alias_args
234234
)
@@ -237,7 +237,7 @@ def _update_parsed_globals(self, parsed_alias_args, parsed_globals):
237237
# global parameters provided in the alias before updating
238238
# the original provided global parameter values
239239
# and passing those onto subsequent commands.
240-
emit_top_level_args_parsed_event(self._session, parsed_alias_args)
240+
emit_top_level_args_parsed_event(self._session, parsed_alias_args, remaining)
241241
for param_name in global_params_to_update:
242242
updated_param_value = getattr(parsed_alias_args, param_name)
243243
setattr(parsed_globals, param_name, updated_param_value)

awscli/argprocess.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class TooComplexError(Exception):
6565

6666

6767
def unpack_argument(
68-
session, service_name, operation_name, cli_argument, value
68+
session, service_name, operation_name, cli_argument, value, parsed_globals
6969
):
7070
"""
7171
Unpack an argument's value from the commandline. This is part one of a two
@@ -83,6 +83,7 @@ def unpack_argument(
8383
value=value,
8484
service_name=service_name,
8585
operation_name=operation_name,
86+
parsed_globals=parsed_globals,
8687
)
8788

8889
if value_override is not None:

awscli/clidriver.py

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
ServiceHelpCommand,
4949
)
5050
from awscli.plugin import load_plugins
51-
from awscli.utils import emit_top_level_args_parsed_event, write_exception, create_nested_client
51+
from awscli.utils import emit_top_level_args_parsed_event, write_exception, create_nested_client, resolve_v2_debug_mode
5252
from botocore import __version__ as botocore_version
5353
from botocore import xform_name
5454

@@ -225,7 +225,7 @@ def main(self, args=None):
225225
# that exceptions can be raised, which should have the same
226226
# general exception handling logic as calling into the
227227
# command table. This is why it's in the try/except clause.
228-
self._handle_top_level_args(parsed_args)
228+
self._handle_top_level_args(parsed_args, remaining)
229229
self._emit_session_event(parsed_args)
230230
HISTORY_RECORDER.record(
231231
'CLI_VERSION', self.session.user_agent(), 'CLI'
@@ -279,8 +279,8 @@ def _show_error(self, msg):
279279
sys.stderr.write(msg)
280280
sys.stderr.write('\n')
281281

282-
def _handle_top_level_args(self, args):
283-
emit_top_level_args_parsed_event(self.session, args)
282+
def _handle_top_level_args(self, args, remaining):
283+
emit_top_level_args_parsed_event(self.session, args, remaining)
284284
if args.profile:
285285
self.session.set_config_variable('profile', args.profile)
286286
if args.region:
@@ -542,9 +542,15 @@ def __call__(self, args, parsed_globals):
542542
event, parsed_args=parsed_args, parsed_globals=parsed_globals
543543
)
544544
call_parameters = self._build_call_parameters(
545-
parsed_args, self.arg_table
545+
parsed_args, self.arg_table, parsed_globals
546546
)
547547

548+
self._detect_binary_file_migration_change(
549+
self._session,
550+
parsed_args,
551+
parsed_globals,
552+
self.arg_table
553+
)
548554
event = f'calling-command.{self._parent_name}.{self._name}'
549555
override = self._emit_first_non_none_response(
550556
event,
@@ -590,7 +596,7 @@ def _add_help(self, parser):
590596
# CLIArguments for values.
591597
parser.add_argument('help', nargs='?')
592598

593-
def _build_call_parameters(self, args, arg_table):
599+
def _build_call_parameters(self, args, arg_table, parsed_globals):
594600
# We need to convert the args specified on the command
595601
# line as valid **kwargs we can hand to botocore.
596602
service_params = {}
@@ -601,19 +607,19 @@ def _build_call_parameters(self, args, arg_table):
601607
py_name = arg_object.py_name
602608
if py_name in parsed_args:
603609
value = parsed_args[py_name]
604-
value = self._unpack_arg(arg_object, value)
610+
value = self._unpack_arg(arg_object, value, parsed_globals)
605611
arg_object.add_to_params(service_params, value)
606612
return service_params
607613

608-
def _unpack_arg(self, cli_argument, value):
614+
def _unpack_arg(self, cli_argument, value, parsed_globals):
609615
# Unpacks a commandline argument into a Python value by firing the
610616
# load-cli-arg.service-name.operation-name event.
611617
session = self._session
612618
service_name = self._operation_model.service_model.endpoint_prefix
613619
operation_name = xform_name(self._name, '-')
614620

615621
return unpack_argument(
616-
session, service_name, operation_name, cli_argument, value
622+
session, service_name, operation_name, cli_argument, value, parsed_globals
617623
)
618624

619625
def _create_argument_table(self):
@@ -661,6 +667,46 @@ def _create_operation_parser(self, arg_table):
661667
parser = ArgTableArgParser(arg_table)
662668
return parser
663669

670+
def _detect_binary_file_migration_change(
671+
self,
672+
session,
673+
parsed_args,
674+
parsed_globals,
675+
arg_table
676+
):
677+
if (
678+
session.get_scoped_config()
679+
.get('cli_binary_format', None) == 'raw-in-base64-out'
680+
):
681+
# if cli_binary_format is set to raw-in-base64-out, then v2 behavior will
682+
# be the same as v1, so there is no breaking change in this case.
683+
return
684+
if resolve_v2_debug_mode(parsed_globals):
685+
parsed_args_to_check = {
686+
arg: getattr(parsed_args, arg)
687+
for arg in vars(parsed_args) if getattr(parsed_args, arg)
688+
}
689+
690+
arg_values_to_check = [
691+
arg.py_name for arg in arg_table.values()
692+
if arg.py_name in parsed_args_to_check
693+
and arg.argument_model.type_name == 'blob'
694+
]
695+
if arg_values_to_check:
696+
print(
697+
'\nAWS CLI v2 UPGRADE WARNING: When specifying a '
698+
'blob-type parameter, AWS CLI v2 will assume the '
699+
'parameter value is base64-encoded. This is different '
700+
'from v1 behavior, where the AWS CLI will automatically '
701+
'encode the value to base64. To retain v1 behavior in '
702+
'AWS CLI v2, set the `cli_binary_format` configuration '
703+
'variable to `raw-in-base64-out`. See '
704+
'https://docs.aws.amazon.com/cli/latest/userguide/'
705+
'cliv2-migration-changes.html'
706+
'#cliv2-migration-binaryparam.\n',
707+
file=sys.stderr
708+
)
709+
664710

665711
class CLIOperationCaller:
666712
"""Call an AWS operation and format the response."""

awscli/customizations/cliinputjson.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def add_to_call_parameters(self, call_parameters, parsed_args,
7070
try:
7171
# Try to load the JSON string into a python dictionary
7272
input_data = json.loads(retrieved_json)
73+
self._session.register(
74+
f"get-cli-input-json-data",
75+
lambda **inner_kwargs: input_data
76+
)
7377
except ValueError as e:
7478
raise ParamError(
7579
self.name, "Invalid JSON: %s\nJSON received: %s"

awscli/customizations/cloudformation/deploy.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424

2525
from awscli.customizations.commands import BasicCommand
2626
from awscli.compat import get_stdout_text_writer
27-
from awscli.utils import create_nested_client, write_exception
27+
from awscli.customizations.utils import uni_print
28+
from awscli.utils import create_nested_client, write_exception, resolve_v2_debug_mode
2829

2930
LOG = logging.getLogger(__name__)
3031

@@ -316,18 +317,33 @@ def _run_main(self, parsed_args, parsed_globals):
316317
s3_uploader = None
317318

318319
deployer = Deployer(cloudformation_client)
320+
v2_debug = resolve_v2_debug_mode(parsed_globals)
319321
return self.deploy(deployer, stack_name, template_str,
320322
parameters, parsed_args.capabilities,
321323
parsed_args.execute_changeset, parsed_args.role_arn,
322324
parsed_args.notification_arns, s3_uploader,
323325
tags, parsed_args.fail_on_empty_changeset,
324-
parsed_args.disable_rollback)
326+
parsed_args.disable_rollback, v2_debug)
325327

326328
def deploy(self, deployer, stack_name, template_str,
327329
parameters, capabilities, execute_changeset, role_arn,
328330
notification_arns, s3_uploader, tags,
329-
fail_on_empty_changeset=True, disable_rollback=False):
331+
fail_on_empty_changeset=True, disable_rollback=False,
332+
v2_debug=False):
330333
try:
334+
if v2_debug and fail_on_empty_changeset:
335+
uni_print(
336+
'\nAWS CLI v2 UPGRADE WARNING: In AWS CLI v2, deploying '
337+
'an AWS CloudFormation Template that results in an empty '
338+
'changeset will NOT result in an error by default. This '
339+
'is different from v1 behavior, where empty changesets '
340+
'result in an error by default. To migrate to v2 behavior '
341+
'and resolve this warning, you can add the '
342+
'`--no-fail-on-empty-changeset` flag to the command. '
343+
'See https://docs.aws.amazon.com/cli/latest/userguide/'
344+
'cliv2-migration-changes.html#cliv2-migration-cfn.\n',
345+
out_file=sys.stderr
346+
)
331347
result = deployer.create_and_wait_for_changeset(
332348
stack_name=stack_name,
333349
cfn_template=template_str,

awscli/customizations/commands.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ def __call__(self, args, parsed_globals):
154154
'custom',
155155
self.name,
156156
cli_argument,
157-
value
157+
value,
158+
parsed_globals
158159
)
159160

160161
# If this parameter has a schema defined, then allow plugins

0 commit comments

Comments
 (0)