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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ Usage of /: 39.6% of 1.94GB Swap usage: 0% Users logged in: 0
jasonsha@MyVM:~$
```

#### Exit Codes
For scripting purposes, we output certain exit codes for differing scenarios.
`0`: Command ran successfully.
`1`: Generic error; server returned bad status code, CLI validation failed, etc.
`2`: Parser error; check input to command line.
`3`: Missing ARM resource; used for existence check from `show` commands.

#### More Samples and Snippets
For more usage examples, take a look at our [GitHub samples repo](http://github.com/Azure/azure-cli-samples) or [https://docs.microsoft.com/en-us/cli/azure/overview](https://docs.microsoft.com/en-us/cli/azure/overview).

Expand Down
4 changes: 2 additions & 2 deletions doc/command_guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ e.g. `keyvault create` instead of `keyvault vault create` (where `keyvault` only
The following are standard names and behavioral descriptions for CRUD commands commonly found within the CLI. These standard command types MUST be followed for consistency with the rest of the CLI.

- `CREATE` - standard command to create a new resource. Usually backed server-side by a PUT request. 'create' commands should be idempotent and should return the resource that was created.
- `UPDATE` - command to selectively update properties of a resource and preserve existing values. May be backed server-side by either a PUT or PATCH request, but the behavior of the command should always be PATCH-like. All `update` commands should be registered using the `generic_update_command` helper to expose the three generic update properties. `update` commands MAY also allow for create-like behavior (PUTCH) in cases where a dedicated `create` command is deemed unnecessary. `update` commands should return the updated resource.
- `UPDATE` - command to selectively update properties of a resource and preserve existing values. May be backed server-side by either a PUT or PATCH request, but the behavior of the command should always be PATCH-like. All `update` commands should be registered using the `generic_update_command` helper to expose the three generic update properties. `update` commands MAY also allow for create-like behavior (PATCH) in cases where a dedicated `create` command is deemed unnecessary. `update` commands should return the updated resource.
- `SET` - command to replace all properties of a resource without preserving existing values, typically backed server-side by a PUT request. This is used when PATCH-like behavior is deemed unnecessary and means that any properties not specifies are reset to their default values. `set` commands are more rare compared to `update` commands. `set` commands should return the updated resource.
- `SHOW` - command to show the properties of a resource, backed server-side by a GET request. Note, please ensure `404(Not Found)` is always trapped by using `exception_handler=empty_on_404` or catching the exception in your custom code. For context, refer to [issue](https://github.com/Azure/azure-cli/issues/6324).
- `SHOW` - command to show the properties of a resource, backed server-side by a GET request. All `show` commands should be registered using the `generic_show_command` helper to ensure `404(Not Found)` is always returning an exit code of 3.
- `LIST` - command to list instances of a resource, backed server-side by a GET request. When there are multiple "list-type" commands within an SDK to list resources at different levels (for example, listing resources in a subscription vice in a resource group) the functionality should be exposed by have a single list command with arguments to control the behavior. For example, if `--resource-group` is provided, the command will call `list_by_resource_group`; otherwise, it will call `list_by_subscription`.
- `DELETE` - command to delete a resource, backed server-side by a DELETE request. Delete commands return nothing on success.

Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli-core/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Release History

2.0.35
++++++
* Minor fixes
* Added method of registering `show` commands to fail with exit code of 3.

2.0.34
++++++
Expand Down
15 changes: 14 additions & 1 deletion src/azure-cli-core/azure/cli/core/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ def generic_update_command(self, name,
**merged_kwargs)

def generic_wait_command(self, name, getter_name='get', getter_type=None, **kwargs):
from azure.cli.core.commands.arm import _cli_generic_update_command, _cli_generic_wait_command
from azure.cli.core.commands.arm import _cli_generic_wait_command
self._check_stale()
merged_kwargs = _merge_kwargs(kwargs, self.group_kwargs, CLI_COMMAND_KWARGS)
# don't inherit deprecation info from command group
Expand All @@ -889,3 +889,16 @@ def generic_wait_command(self, name, getter_name='get', getter_type=None, **kwar
'{} {}'.format(self.group_name, name),
getter_op=getter_op,
**merged_kwargs)

def generic_show_command(self, name, getter_name='get', getter_type=None, **kwargs):
from azure.cli.core.commands.arm import _cli_generic_show_command
self._check_stale()
merged_kwargs = _merge_kwargs(kwargs, self.group_kwargs, CLI_COMMAND_KWARGS)
if getter_type:
merged_kwargs = _merge_kwargs(getter_type.settings, merged_kwargs, CLI_COMMAND_KWARGS)
getter_op = self._resolve_operation(merged_kwargs, getter_name, getter_type)
_cli_generic_show_command(
self.command_loader,
'{} {}'.format(self.group_name, name),
getter_op=getter_op,
**merged_kwargs)
61 changes: 49 additions & 12 deletions src/azure-cli-core/azure/cli/core/commands/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
logger = get_logger(__name__)


# pylint:disable=too-many-lines
class ArmTemplateBuilder(object):

def __init__(self):
Expand Down Expand Up @@ -339,6 +340,14 @@ def _get_client_factory(_, kwargs):
return factory


def get_arguments_loader(context, getter_op, cmd_args=None):
getter_args = dict(extract_args_from_signature(context.get_op_handler(getter_op), excluded_params=EXCLUDED_PARAMS))
cmd_args = cmd_args or {}
cmd_args.update(getter_args)
cmd_args['cmd'] = CLICommandArgument('cmd', arg_type=ignore_type)
return cmd_args


# pylint: disable=too-many-statements
def _cli_generic_update_command(context, name, getter_op, setter_op, setter_arg_name='parameters',
child_collection_prop_name=None, child_collection_key='name',
Expand All @@ -353,9 +362,6 @@ def _cli_generic_update_command(context, name, getter_op, setter_op, setter_arg_
raise TypeError("Custom function operation must be a string. Got '{}'".format(
custom_function_op))

def get_arguments_loader():
return dict(extract_args_from_signature(context.get_op_handler(getter_op), excluded_params=EXCLUDED_PARAMS))

def set_arguments_loader():
return dict(extract_args_from_signature(context.get_op_handler(setter_op), excluded_params=EXCLUDED_PARAMS))

Expand All @@ -368,10 +374,8 @@ def function_arguments_loader():
return dict(extract_args_from_signature(custom_op, excluded_params=EXCLUDED_PARAMS))

def generic_update_arguments_loader():

arguments = {}
arguments = get_arguments_loader(context, getter_op)
arguments.update(set_arguments_loader())
arguments.update(get_arguments_loader())
arguments.update(function_arguments_loader())
arguments.pop('instance', None) # inherited from custom_function(instance, ...)
arguments.pop('parent', None)
Expand Down Expand Up @@ -406,7 +410,6 @@ def __call__(self, parser, namespace, values, option_string=None):
help='Remove a property or an element from a list. Example: {}'.format(remove_usage),
metavar='LIST INDEX', arg_group=group_name
)
arguments['cmd'] = CLICommandArgument('cmd', arg_type=ignore_type)
return [(k, v) for k, v in arguments.items()]

def _extract_handler_and_args(args, commmand_kwargs, op):
Expand Down Expand Up @@ -527,10 +530,7 @@ def _cli_generic_wait_command(context, name, getter_op, **kwargs):
factory = _get_client_factory(name, kwargs)

def generic_wait_arguments_loader():

getter_args = dict(extract_args_from_signature(context.get_op_handler(getter_op),
excluded_params=EXCLUDED_PARAMS))
cmd_args = getter_args.copy()
cmd_args = get_arguments_loader(context, getter_op)

group_name = 'Wait Condition'
cmd_args['timeout'] = CLICommandArgument(
Expand Down Expand Up @@ -563,7 +563,6 @@ def generic_wait_arguments_loader():
"provisioningState!='InProgress', "
"instanceView.statuses[?code=='PowerState/running']"
)
cmd_args['cmd'] = CLICommandArgument('cmd', arg_type=ignore_type)
return [(k, v) for k, v in cmd_args.items()]

def get_provisioning_state(instance):
Expand Down Expand Up @@ -646,6 +645,44 @@ def handler(args):
context._cli_command(name, handler=handler, argument_loader=generic_wait_arguments_loader, **kwargs) # pylint: disable=protected-access


def _cli_generic_show_command(context, name, getter_op, **kwargs):

if not isinstance(getter_op, string_types):
raise ValueError("Getter operation must be a string. Got '{}'".format(type(getter_op)))

factory = _get_client_factory(name, kwargs)

def generic_show_arguments_loader():
cmd_args = get_arguments_loader(context, getter_op)
return [(k, v) for k, v in cmd_args.items()]

def handler(args):
from azure.cli.core.commands.client_factory import resolve_client_arg_name

cmd = args.get('cmd')
operations_tmpl = _get_operations_tmpl(cmd)
getter_args = dict(extract_args_from_signature(context.get_op_handler(getter_op),
excluded_params=EXCLUDED_PARAMS))
client_arg_name = resolve_client_arg_name(operations_tmpl, kwargs)
try:
client = factory(context.cli_ctx) if factory else None
except TypeError:
client = factory(context.cli_ctx, args) if factory else None
if client and (client_arg_name in getter_args or client_arg_name == 'self'):
args[client_arg_name] = client

getter = context.get_op_handler(getter_op)
try:
return getter(**args)
except Exception as ex: # pylint: disable=broad-except
if getattr(ex, 'status_code', None) == 404:
logger.error(getattr(ex, 'message', ex))
import sys
sys.exit(3)
raise
context._cli_command(name, handler=handler, argument_loader=generic_show_arguments_loader, **kwargs) # pylint: disable=protected-access


def verify_property(instance, condition):
from jmespath import compile as compile_jmespath
result = todict(instance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
g.command('check-name', 'check_name_availability')
g.custom_command('create', 'create_storage_account', min_api='2016-01-01')
g.command('delete', 'delete', confirmation=True)
g.command('show', 'get_properties', exception_handler=g.get_handler_suppress_404())
g.generic_show_command('show', 'get_properties')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this represent a breaking change to this command? If we are including this on one command as a test, it should ensure that the behavior is unchanged. We don't want to go from swallowing a 404 to failing. Better to go from failing to failing. We would need to advertise the behavior change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, the g.get_handler_suppress_404() only handles data-plane storage 404 exceptions.
This is an example of an exception handler that was added but never did its intended job.

This won't break any scripts unless the user checked specifically for an exit code of 1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would they have previously looked for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample script from #5006 :


if [ $? != 0 ]; then
...
fi

Most script will check for exit code not being 0 or the presence of output, as error codes can vary.

g.custom_command('list', 'list_storage_accounts')
g.custom_command('show-usage', 'show_storage_account_usage')
g.custom_command('show-connection-string', 'show_storage_account_connection_string')
Expand Down
Loading