Professional Documents
Culture Documents
This document describes the command guidelines for 'az' and applies to both CLI
command modules and extensions.
If in doubt, ask!
## General Patterns
- Be consistent with POSIX tools (support piping, work with grep, awk, jq, etc.)
- Support tab completion for parameter names and values (e.g. resource names)
- Commands must support all output types (json, tsv, table)
- Provide custom table outputs for commands that don't provide table output
automatically
- Commands must return an object, dictionary or `None` (do not string, Boolean,
etc. types)
- Command output must go to `stdout`, everything else to `stderr`
(log/status/errors).
- Log to `logger.error()` or `logger.warning()` for user messages; do not use the
`print()` function
- Use the appropriate logging level for printing strings. e.g.
`logging.info(“Upload of myfile.txt successful”)` NOT `return “Upload successful”`.
```
Group
az keyvault: Safeguard and maintain control of keys, secrets, and
certificates.
Subgroups:
certificate : Manage certificates.
key : Manage keys.
secret : Manage secrets.
Commands:
create : Create a key vault.
delete : Delete a key vault.
delete-policy: Delete security policy settings for a Key Vault.
list : List key vaults.
list-deleted : Gets information about the deleted vaults in a subscription.
purge : Permanently deletes the specified vault.
recover : Recover a key vault.
set-policy : Update security policy settings for a Key Vault.
show : Show details of a key vault.
update : Update the properties of a key vault.
```
To create a vault, you simply use `az keyvault create ...`. An alternative
would be to place the vault commands into a separate subgroup, like this:
```
Group
az keyvault: Safeguard and maintain control of keys, secrets, and
certificates.
Subgroups:
certificate : Manage certificates.
key : Manage keys.
secret : Manage secrets.
vault : Manage vaults.
```
Now, to create a vault, you have to use `az keyvault vault create ...` which is
overly verbose adds unnecessary depth to the tree. The preferred style makes the
command use more convenient and intuitive.
</p>
</details>
## Argument Naming Conventions
**AVOID**
```
--parameters-url URL to a parameters file.
--parameters-path Local path to a parameters fle.
```
**PREFERRED**
```
--parameters Local path or URL to a parameters file.`
```
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.
## Non-standard Commands
For commands that don't conform to one of the above-listed standard command
patterns, use the following guidance.
- (*) Don't use single word verbs if they could cause confusion with the standard
command types. For example, don't use `get` or `new` as these sound functionally
the same as `show` and `create` respectively, leading to confusion as to what the
expected behavior should be.
- (*) Descriptive, hyphenated command names are often a better option than single
verbs.
Certain endpoints accept complex object that themselves contain other complex
objects or collections of complex objects. Since this child resources lack
endpoints of their own, it can often be difficult to craft CLI commands to manage
such objects. The tendency is to simply accept a JSON blob for these arguments.
**THIS PRACTICE IS UNACCEPTABLE.** Existing instances of JSON strings have
demonstrably frustrated customers because (1) the required format is inadequately
(if at all) conveyed, (2) inputting JSON strings on the command line is tedious and
extremely difficult on certain shells, necessitating error-prone escaping and (3)
parsing errors are difficult to troubleshoot.
The simplest case is when a complex parameter is a single object with simple
properties. In this case, the simplest approach is to expose the properties of the
complex parameter as individual parameters on the parent resource and then assemble
the complex parameter inside a custom command (most common) or validator.
For example, consider the following structure for an object of type Foo:
```
{
name: str,
type: str,
policy: {
action: enum,
values: list(str)
}
}
```
Best practice would be to group the policy arguments together in help using the
`arg_group` keyword, like so:
```Python
with self.argument_context('foo') as c:
c.argument('name', options_list=['--name', '-n'], ...)
Often a resource contains collections of child objects. The simplest way to handle
these situations is to give the complex collection its own command subgroup.
Consider our Foo resource. Let's add a complex collection:
```
{
name: str,
...
rules: [
{
name: str,
metric: enum,
operation: enum,
value: str
},
...
]
}
```
In this case, the most idiomatic and straight-forward solution would be to expose a
group of `rule` subcommands. The foo create command would look like:
```Python
def create_foo(cmd, client, resource_group_name, foo_name, policy_action,
policy_values):
Foo, Policy = cmd.get_models('Foo', 'Policy')
foo_obj = Foo(
rules=[], # if service fails, see: Overcoming Service Limitations
policy=Policy(
action=policy_action,
values=policy_values
)
)
return client.create_or_update(foo_name, resource_group_name, foo_obj)
```
The usage pattern of this implementation to create two rules would be:
```
az foo create -g MyRG -n myfoo --action allow --values test prod
az foo rule create -g MyRG --foo-name myfoo -n rule1 --metric name --operations
equals --value test
az foo rule create -g MyRG --foo-name myfoo -n rule2 --metric age --operations
lessThan --value 100
```
Given that, consider how we could implement the earlier example, this time using
actions on the `foo create` command instead of subcommands to manage the
collection:
```
{
name: str,
...
rules: [
{
name: str,
metric: enum,
operation: enum,
value: str
},
...
]
}
```
The rule action will need some way to parse out several arguments from a single
string. There are two ways of doing this. One would be to make the syntax of the
action
use positional arguments. This works well when there is an obvious positional
relationship between the parameters. The other would be to accept key=value pairs.
This will create a much more verbose command string, but may be appropriate when
there's no positional relationship between the arguments. Here is an example of
each.
The usage pattern of this implementation to create two rules would be:
```
az foo create -g MyRG -n myfoo --action allow --values test prod --rule rule1 name
equals test --rule rule2 age lessThan 100
```
The usage pattern of this implementation to create two rules would be:
```
az foo create -g MyRG -n myfoo --action allow --values test prod --rule name=rule1
metric=name operation=equals value=test --rule name=rule2 metric=age
operation=lessThan value=100
```
There are times when service limitations can interfere with the recommended
approach of handling complex collections using subcommand groups. In our example,
the service might fail if the `foo_create` command supplied an empty rule
collection. This is something we can work around in the CLI using the `--defer`
caching mechanism. The following changes should be made:
Update the custom commands to use the `cached_get` and `cached_put` helpers:
```Python
def create_foo(cmd, client, resource_group_name, foo_name, policy_action,
policy_values):
from azure.cli.core.commands import cached_get, cached_put
Foo, Policy = cmd.get_models('Foo', 'Policy')
foo_obj = Foo(
rules=[],
policy=Policy(
action=policy_action,
values=policy_values
)
)
# cache the payload if --defer used or send to Azure
return cached_put(cmd, client.create_or_update, resource_group_name, foo_name,
foo_obj)
```
The usage pattern of this implementation to create two rules would be:
```
az foo create -g MyRG -n myfoo --action allow --values test prod --defer
az foo rule create -g MyRG --foo-name myfoo -n rule1 --metric name --operations
equals --value test --defer
az foo rule create -g MyRG --foo-name myfoo -n rule2 --metric age --operations
lessThan --value 100
```
Several services support the concept of network rules. To drive consistency across
services, command authors for these services should use the following as guidance.
Questions/exceptions should be directed to the CLI team.
- The parent resource should expose a single command group called `network-rule`
with three commands: `add`, `remove`, `list`.
- Internally, network rules are typically modeled as a rule set. This
implementation is largely hidden from the user since these services only allow a
single rule set. If there are properties that can be set on the rule set generally
(the only known one as of this writing is `default_action`, these properties should
be exposed on the `create/update` commands of the parent object, under an argument
group called `Network Rule`.
- The following examples assume that individual rules do not have names. If your
rules do have names, consult the CLI team for guidance.
- The `... network-rule add` command should look similar to the following,
depending on which features are supported by the service.
```
Arguments
--name -n [Required] : The name of the [PARENT RESOURCE].
--resource-group -g : Name of resource group. You can configure the
default group using `az
configure --defaults group=<name>`.
- The `... network-rule remove` command should look similar to the following,
depending on which features are supported by the service.
```
Arguments
--name -n [Required] : The name of the [PARENT RESOURCE].
--resource-group -g : Name of resource group. You can configure the
default group using
`az configure --defaults group=<name>`.
## Error Handling
## Coding Practices