Now we have some use cases about set/unset properties, try to redefine devref to find out a best and easiest solution to keep commands simple and clearly. Five use cases exist in property action, "append", "update", "remove", "clean", "override", the following rules can cover all these use cases: 1. append ==> "set --property new-key=value" 2. update ==> "set --property existed-key=new-value" 3. remove ==> "unset --property existed-key" 4. clean ==> "set --no-property" 5. clean ==> "unset --all-property" 6. override ==> "set --no-property --property new-key=value" Related blueprint support-no-property-in-aggregate and blueprint allow-overwrite-set-options. Change-Id: If86daf6989d8e0ad0dc6e590d7636be7d5203a18
9.7 KiB
Command Options
OpenStackClient commands all have a set of zero or more options unique to the command, however there are of course ways in which these options are common and consistent across all of the commands that include them.
These are the set of guidelines for OSC developers that help keep the interface and commands consistent.
In some cases (like the boolean variables below) we use the same pattern for defining and using options in all situations. The alternative of only using it when necessary leads to errors when copy-n-paste is used for a new command without understanding why or why not that instance is correct.
The Human Interface Guide <humaninterfaceguide>
describes the guildelines for option names and usage. In short: * All
option names shall be GNU-style long names (two leading dashes). * Some
global options may have short names, generally limited to those defined
in support libraries such as cliff
.
General Command Options
Boolean Options
Boolean options for any command that sets a resource state, such as 'enabled' or 'public', shall always have both positive and negative options defined. The names of those options shall either be a naturally occurring pair of words (in English) or a positive option and a negative option with no- prepended (such as in the traditional GNU option usage) like --share and --no-share.
In order to handle those APIs that behave differently when a field is set to None and when the field is not present in a passed argument list or dict, each of the boolean options shall set its own variable to True as part of a mutually exclusive group, rather than the more common configuration of setting a single destination variable True or False directly. This allows us to detect the situation when neither option is present (both variables will be False) and act accordingly for those APIs where this matters.
This also requires that each of the boolean values be tested in the take_action() method to correctly set (or not) the underlying API field values.
--enable
Enable <resource> (default)
--disable
Disable <resource>
Implementation
The parser declaration should look like this:
= parser.add_mutually_exclusive_group()
enable_group
enable_group.add_argument('--enable',
='store_true',
actionhelp=_('Enable <resource> (default)'),
)
enable_group.add_argument('--disable',
='store_true',
actionhelp=_('Disable <resource>'),
)
An example handler in `take_action()`:
# This leaves 'enabled' undefined if neither option is present
if parsed_args.enable:
'enabled'] = True
kwargs[if parsed_args.disable:
'enabled'] = False kwargs[
Options with Choices
Some options have a specific set of values (or choices) that are valid. These choices may be validated by the CLI. If the underlying API is stable and the list of choices are unlikely to change then the CLI may validate the choices. Otherwise, the CLI must defer validation of the choices to the API. If the option has a default choice then it must be documented.
Having the CLI validate choices will be faster and may provide a
better error message for the user if an invalid choice is specified (for
example:
argument --test: invalid choice: 'choice4' (choose from 'choice1', 'choice2', 'choice3')
).
The trade-off is that CLI changes are required in order to take
advantage of new choices.
Implementation
An example parser declaration:
choice_option.add_argument('--test',
='<test>',
metavar=['choice1', 'choice2', 'choice3'],
choiceshelp=_('Test type (choice1, choice2 or choice3)'),
)
Options with Multiple Values
Some options can be repeated to build a collection of values for a
property. Adding a value to the collection must be provided via the
set
action. Removing a value from the collection must be
provided via an unset
action. As a convenience, removing
all values from the collection may be provided via a --no
option on the set
action and a --all
option on
unset
action. If both --no
option and option
are specified, the values specified on the command would overwrite the
collection property instead of appending on the set
action.
The --all
option must be part of a mutually exclusive group
with the related property option on the unset
action,
overwrite case don't exist in unset
action.
An example behavior for set
action:
Append:
object set --example-property xxx
Overwrite:
object set --no-example-property --example-property xxx
The example below assumes a property that contains a list of unique values. However, this example can also be applied to other collections using the appropriate parser action and action implementation (e.g. a dict of key/value pairs). Implementations will vary depending on how the REST API handles adding/removing values to/from the collection and whether or not duplicate values are allowed.
Implementation
An example parser declaration for set action:
parser.add_argument('--example-property',
='<example-property>',
metavar='example_property',
dest='append',
actionhelp=_('Example property for this <resource> '
'(repeat option to set multiple properties)'),
)
parser.add_argument('--no-example-property',
='no_example_property',
dest='store_true',
actionhelp=_('Remove all example properties for this <resource> '
'(specify both --example-property and --no-example-property'
' to overwrite the current example properties)'),
)
An example handler in take_action() for set action:
if parsed_args.example_property and parsed_args.no_example_property:
'example_property'] = parsed_args.example_property
kwargs[elif parsed_args.example_property:
'example_property'] = \
kwargs[+ parsed_args.example_property
resource_example_property elif parsed_args.no_example_property:
'example_property'] = [] kwargs[
An example parser declaration for unset action:
= parser.add_mutually_exclusive_group()
example_property_group
example_property_group.add_argument('--example-property',
='<example-property>',
metavar='example_property',
dest='append',
actionhelp=_('Example property for this <resource> '
'(repeat option to remove multiple properties)'),
)
example_property_group.add_argument('--all-example-property',
='all_example_property',
dest='store_true',
actionhelp=_('Remove all example properties for this <resource>'),
)
An example handler in take_action() for unset action:
if parsed_args.example_property:
'example_property'] = \
kwargs[list(set(resource_example_property) - \
set(parsed_args.example_property))
if parsed_args.all_example_property:
'example_property'] = [] kwargs[
Required Options
Some options have no default value and the API does not allow them to be None, then these options are always required when users use the command to which these options belong.
Required options must be validated by the CLI to avoid omissions. The
CLI validation may provide an error message for the user if a required
option is not specified. (for example:
error: argument --test is required
)
--test
Test option (required)
Implementation
The parser declaration should look like this:
parser.add_argument('--test',
='<test>',
metavar=True,
requiredhelp=_('Test option (required)'),
)
List Command Options
Additional Fields
Most list commands only return a subset of the available fields by default. Additional fields are available with the --long option. All list commands should allow --long even if they return all fields by default.
--long
List additional fields in output
Implementation
The parser declaration should look like this:
parser.add_argument('--long',
='store_true',
action=False,
defaulthelp='List additional fields in output',
)
Pagination
There are many ways to do pagination, some OpenStack APIs support it, some don't. OpenStackClient attempts to define a single common way to specify pagination on the command line.
--marker <marker>
Anchor for paging
--limit <limit>
Limit number of <resource> returned (integer)
Implementation
The parser declaration should look like this:
parser.add_argument("--marker",
="<marker>",
metavarhelp="Anchor for paging",
)
parser.add_argument("--limit",
="<limit>",
metavartype=int,
help="Limit the number of <resource> returned",
)