Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-ErrorMessages-78207.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "Error Messages",
"description": "Standardize CLI error message format to use `aws: [ERROR]: <message>` prefix for consistency across all error types."
}
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-Output-51826.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "Output",
"description": "Add support for ``--output off`` to suppress all stdout output while preserving stderr for errors and warnings."
}
5 changes: 5 additions & 0 deletions .changes/next-release/feature-Output-59989.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "Output",
"description": "Add structured error output with configurable formats. CLI errors now display additional fields in the configured format (legacy, json, yaml, text, table, or enhanced). Configure via ``--cli-error-format``, ``cli_error_format`` config variable, or ``AWS_CLI_ERROR_FORMAT`` environment variable. The new enhanced format is the default. Set ``cli_error_format=legacy`` to preserve the original error format."
}
3 changes: 1 addition & 2 deletions awscli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ def error(self, message):
should raise an exception.
"""
usage_message = self.format_usage()
error_message = f'{self.prog}: [ERROR]: {message}'
raise ArgParseException(f'{error_message}\n\n{usage_message}')
raise ArgParseException(f'{message}\n\n{usage_message}')


class MainArgParser(CLIArgParser):
Expand Down
34 changes: 28 additions & 6 deletions awscli/clidriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def create_clidriver(args=None):
session.full_config.get('plugins', {}),
event_hooks=session.get_component('event_emitter'),
)
error_handlers_chain = construct_cli_error_handlers_chain()
error_handlers_chain = construct_cli_error_handlers_chain(session)
driver = CLIDriver(
session=session, error_handler=error_handlers_chain, debug=debug
)
Expand Down Expand Up @@ -246,7 +246,9 @@ def __init__(self, session=None, error_handler=None, debug=False):
self.session = session
self._error_handler = error_handler
if self._error_handler is None:
self._error_handler = construct_cli_error_handlers_chain()
self._error_handler = construct_cli_error_handlers_chain(
self.session
)
if debug:
self._set_logging(debug)
self._update_config_chain()
Expand Down Expand Up @@ -275,6 +277,9 @@ def _update_config_chain(self):
config_store.set_config_provider(
'cli_help_output', self._construct_cli_help_output_chain()
)
config_store.set_config_provider(
'cli_error_format', self._construct_cli_error_format_chain()
)

def _construct_cli_region_chain(self):
providers = [
Expand Down Expand Up @@ -368,6 +373,20 @@ def _construct_cli_auto_prompt_chain(self):
]
return ChainProvider(providers=providers)

def _construct_cli_error_format_chain(self):
providers = [
EnvironmentProvider(
name='AWS_CLI_ERROR_FORMAT',
env=os.environ,
),
ScopedConfigProvider(
config_var_name='cli_error_format',
session=self.session,
),
ConstantProvider(value='enhanced'),
]
return ChainProvider(providers=providers)

@property
def subcommand_table(self):
return self._get_command_table()
Expand Down Expand Up @@ -516,6 +535,7 @@ def main(self, args=None):
command_table = self._get_command_table()
parser = self.create_parser(command_table)
self._add_aliases(command_table, parser)
parsed_args = None
try:
# Because _handle_top_level_args emits events, it's possible
# that exceptions can be raised, which should have the same
Expand All @@ -538,6 +558,7 @@ def main(self, args=None):
e,
stdout=get_stdout_text_writer(),
stderr=get_stderr_text_writer(),
parsed_globals=parsed_args,
)

def _emit_session_event(self, parsed_args):
Expand Down Expand Up @@ -818,7 +839,10 @@ def _parse_potential_subcommand(self, args, subcommand_table):
def __call__(self, args, parsed_globals):
# Once we know we're trying to call a particular operation
# of a service we can go ahead and load the parameters.
event = f'before-building-argument-table-parser.{self._parent_name}.{self._name}'
event = (
f'before-building-argument-table-parser.'
f'{self._parent_name}.{self._name}'
)
self._emit(
event,
argument_table=self.arg_table,
Expand Down Expand Up @@ -1028,9 +1052,7 @@ def _make_client_call(
paginator = client.get_paginator(py_operation_name)
response = paginator.paginate(**parameters)
else:
response = getattr(client, xform_name(operation_name))(
**parameters
)
response = getattr(client, py_operation_name)(**parameters)
return response

def _display_response(self, command_name, response, parsed_globals):
Expand Down
9 changes: 5 additions & 4 deletions awscli/customizations/configure/mfalogin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from awscli.customizations.commands import BasicCommand
from awscli.customizations.configure import profile_to_section
from awscli.customizations.configure.writer import ConfigFileWriter
from awscli.errorformat import write_error

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -142,7 +143,7 @@ def _get_mfa_token(self):
'None', 'mfa_token', 'MFA token code'
)
if not token_code:
sys.stderr.write("MFA token code is required\n")
write_error(sys.stderr, "MFA token code is required")
return None
return token_code

Expand All @@ -156,7 +157,7 @@ def _call_sts_get_session_token(self, sts_client, duration_seconds, mfa_serial,
)
return response
except ClientError as e:
sys.stderr.write(f"An error occurred: {e}\n")
write_error(sys.stderr, f"An error occurred: {e}")
return None

def _resolve_mfa_serial(self, parsed_args, source_config):
Expand All @@ -167,7 +168,7 @@ def _resolve_mfa_serial(self, parsed_args, source_config):
'None', 'mfa_serial', 'MFA serial number or ARN'
)
if not mfa_serial:
sys.stderr.write("MFA serial number or MFA device ARN is required\n")
write_error(sys.stderr, "MFA serial number or MFA device ARN is required")
return None
return mfa_serial

Expand Down Expand Up @@ -252,7 +253,7 @@ def _handle_interactive_prompting(self, parsed_args, duration_seconds):
'None', config_name, prompt_text
)
if not value or value == 'None':
sys.stderr.write(f"{prompt_text} is required\n")
write_error(sys.stderr, f"{prompt_text} is required")
return 1
values[config_name] = value

Expand Down
10 changes: 5 additions & 5 deletions awscli/customizations/ecs/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,26 @@ def __init__(self, **kwargs):


class MissingPropertyError(ECSError):
fmt = "Error: Resource '{resource}' must include property '{prop_name}'"
fmt = "Resource '{resource}' must include property '{prop_name}'"


class FileLoadError(ECSError):
fmt = "Error: Unable to load file at {file_path}: {error}"
fmt = "Unable to load file at {file_path}: {error}"


class InvalidPlatformError(ECSError):
fmt = "Error: {resource} '{name}' must support 'ECS' compute platform"
fmt = "{resource} '{name}' must support 'ECS' compute platform"


class InvalidProperyError(ECSError):
fmt = (
"Error: deployment group '{dg_name}' does not target "
"deployment group '{dg_name}' does not target "
"ECS {resource} '{resource_name}'"
)


class InvalidServiceError(ECSError):
fmt = "Error: Service '{service}' not found in cluster '{cluster}'"
fmt = "Service '{service}' not found in cluster '{cluster}'"


class ServiceClientError(ECSError):
Expand Down
2 changes: 1 addition & 1 deletion awscli/customizations/emr/applicationutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def build_applications(region, parsed_applications, ami_version=None):
)
else:
raise ParamValidationError(
'aws: error: AMI version %s is not '
'AMI version %s is not '
'compatible with HBase.' % ami_version
)
elif app_name == constants.IMPALA:
Expand Down
4 changes: 2 additions & 2 deletions awscli/customizations/emr/createcluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def _run_main_command(self, parsed_args, parsed_globals):
)
except ValueError:
raise ParamValidationError(
'aws: error: invalid json argument for '
'invalid json argument for '
'option --configurations'
)

Expand Down Expand Up @@ -709,7 +709,7 @@ def _build_bootstrap_actions(self, cluster, parsed_boostrap_actions):
> constants.MAX_BOOTSTRAP_ACTION_NUMBER
):
raise ParamValidationError(
'aws: error: maximum number of '
'maximum number of '
'bootstrap actions for a cluster exceeded.'
)

Expand Down
2 changes: 1 addition & 1 deletion awscli/customizations/emr/emrutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def apply_boolean_options(
):
if true_option and false_option:
error_message = (
'aws: error: cannot use both '
'cannot use both '
+ true_option_name
+ ' and '
+ false_option_name
Expand Down
Loading
Loading