Command Reference YAML
The YAML files in this directory are used with the
Cisco::CommandReference module as a way to abstract away differences
between client APIs as well as differences between platforms sharing
the same client API.
This document describes the structure and semantics of these files.
- Introduction
- Basic attribute definition
- Advanced attribute definition
- Attribute properties
- Style Guide
Introduction
Each file describes a single 'feature' (a closely related set of
configurable attributes). The YAML within the file defines the set of
'attributes' belonging to this feature. When a CommandReference object
is instantiated, the user can look up any given attribute using the
lookup('feature_name', 'attribute_name') API. Usually, instead of calling
this API directly, node utility classes will call the various Node APIs,
for example:
config_set('feature_name', 'attribute_name', *args)
value = config_get('feature_name', 'attribute_name')
default = config_get_default('feature_name', 'attribute_name')
Basic attribute definition
The simplest definition of an attribute directly sets one or more properties of this attribute. These properties' values can generally be set to any basic Ruby type such as string, boolean, integer, array, or regexp. An example:
# vtp.yaml
domain:
config_get: "show vtp status"
config_get_token: "domain_name"
config_set: "vtp domain <domain>"
filename:
config_get: "show running vtp"
config_get_token: '/vtp file (\S+)/'
config_set: "<state> vtp file <filename>"
default_value: ""
In the above example, two attributes are defined: ('vtp', 'domain') and ('vtp', 'filename').
Note that all attribute properties are optional and may be omitted if not
needed. In the above, example 'domain' does not have a value defined for
default_value but 'filename' does have a default.
Wildcard substitution
The config_get_token and config_set properties (and their associated
_append variants) all support two forms of wildcarding - printf-style and
key-value. Key-value is generally preferred, as described below.
Printf-style wildcards
# tacacs_server_host.yaml
encryption:
config_set: '%s tacacs-server host %s key %s %s'
This permits parameter values to be passed as a simple sequence to generate the resulting string or regexp:
irb(main):009:0> ref = cr.lookup('tacacs_server_host', 'encryption')
irb(main):010:0> ref.config_set('no', 'myhost', 'md5', 'mypassword')
=> ["no tacacs-server host myhost key md5 mypassword"]
Printf-style wildcards are quick to implement and concise, but less flexible - in particular they cannot handle a case where different platforms (or different client APIs!) take parameters in a different order - and less readable in the Ruby code.
Key-value wildcards
# ospf.yaml
auto_cost:
config_set: ['router ospf <name>', 'auto-cost reference-bandwidth <cost> <type>']
This requires parameter values to be passed as a hash:
irb(main):015:0> ref = cr.lookup('ospf', 'auto_cost')
irb(main):016:0> ref.config_set(name: 'red', cost: '40', type: 'Gbps')
=> ["router ospf red", "auto-cost reference-bandwidth 40 Gbps"]
Array elements that contain a parameter that is not included in the argument hash are not included in the result:
irb(main):017:0> ref.config_set(name: 'red', cost: '40')
=> ["router ospf red"]
If this process results in an empty array, then an ArgumentError is raised to indicate that not enough parameters were supplied.
Key-value wildcards are moderately more complex to implement than Printf-style wildcards but they are more readable in the Ruby code and are flexible enough to handle significant platform differences in CLI. Key-value wildcards are therefore the recommended approach for new development.
Advanced attribute definition
_template
The optional _template section can be used to define base parameters for all
attributes of a given feature. For example, all interface attributes might be
checked with the show running-config interface all command, and all
attributes might be set by first entering the interface configuration submode
with the interface <name> configuration command. Thus, you might have:
# interface.yaml
_template:
config_get: 'show running-config interface all'
config_get_token: '/^interface <name>$/'
config_set: 'interface <name>'
access_vlan:
config_get_token_append: '/^switchport access vlan (.*)$/'
config_set_append: 'switchport access vlan <number>'
description:
config_get_token_append: '/^description (.*)$/'
config_set_append: 'description <desc>'
...
instead of the more repetitive (but equally valid):
# interface.yaml
access_vlan:
config_get: 'show running interface all'
config_get_token: ['/^interface <name>$/i', '/^switchport access vlan (.*)$/']
config_set: ['interface <name>', 'switchport access vlan <number>']
description:
config_get: 'show running-config interface all'
config_get_token: ['/^interface <name>$/i', '/^description (.*)$/']
config_set: ['interface <name>', 'description <desc>']
...
Platform and API variants
Clients for different Cisco platforms may use different APIs. Currently the only supported API is NXAPI (CLI-based API used for Cisco Nexus platforms). Often the CLI or other input/output formats (YANG, etc.) needed will vary between APIs, so the YAML must be able to accomodate this.
Any of the attribute properties can be subdivided by platform and API type by using the
combination of API type and platform type as a key. For example, interface VRF membership defaults to "" (no VRF) on both Nexus and IOS XR platforms, but the CLI is 'vrf member
# interface.yaml
vrf:
default_value: ""
cli_nexus:
config_get_token_append: '/^vrf member (.*)/'
config_set_append: "<state> vrf member <vrf>"
and later, once we have a CLI-based API for IOS XR, this could be extended:
# interface.yaml
vrf:
default_value: ""
cli_nexus:
config_get_token_append: '/^vrf member (.*)/'
config_set_append: "<state> vrf member <vrf>"
cli_ios_xr:
config_get_token_append: '/^vrf (.*)/'
config_set_append: "<state> vrf <vrf>"
Product variants
Any of the attribute properties can be subdivided by platform product ID string
using a regexp against the product ID as a key. When one or more regexp keys
are defined thus, you can also use the special key else to provide values
for all products that do not match any of the given regexps:
# show_version.yaml
system_image:
/N9/:
config_get_token: "kick_file_name"
test_config_get_regex: '/.*NXOS image file is: (.*)$.*/'
else:
config_get_token: "isan_file_name"
test_config_get_regex: '/.*system image file is: (.*)$.*/'
_exclude
Related to product variants, an _exclude entry can be used to mark an entire feature or a given feature attribute as not applicable to a particular set of products. For example, if feature 'fabricpath' doesn't apply to the N3K or N9K platforms, it can be excluded altogether from those platforms by a single _exclude entry at the top of the file:
# fabricpath.yaml
---
_exclude: [/N3/, /N9/]
_template:
...
Individual feature attributes can also be excluded in this way:
attribute:
_exclude:
- /N7/
default_value: true
config_get: 'show attribute'
config_set: 'attribute'
When a feature or attribute is excluded in this way, attempting to call config_get or config_set on an excluded node will result in a Cisco::UnsupportedError being raised. Calling config_get_default on such a node will always return nil.
Combinations of these
In many cases, supporting multiple platforms and multiple products will require using several or all of the above options.
Using _template in combination with API variants:
# inventory.yaml
_template:
cli_ios_xr:
config_get: 'show inventory | begin "Rack 0"'
test_config_get: 'show inventory'
cli_nexus:
config_get: 'show inventory'
test_config_get: 'show inventory | no-more'
productid:
cli_ios_xr:
config_get_token: '/PID: ([^ ,]+)/'
cli_nexus:
config_get_token: ["TABLE_inv", "ROW_inv", 0, "productid"]
Using platform variants and product variants together:
# inventory.yaml
description:
config_get_token: "chassis_id"
cli_nexus:
/N7/:
test_config_get_regex: '/.*Hardware\n cisco (\w+ \w+ \(\w+ \w+\) \w+).*/'
else:
test_config_get_regex: '/Hardware\n cisco (([^(\n]+|\(\d+ Slot\))+\w+)/'
cli_ios_xr:
config_get: 'show inventory | inc "Rack 0"'
config_get_token: '/DESCR: "(.*)"/'
test_config_get: 'show inventory | inc "Rack 0"'
test_config_get_regex: '/DESCR: "(.*)"/'
Attribute properties
config_get
config_get must be a single string representing the CLI command (usually a
show command) to be used to display the information needed to get the
current value of this attribute.
# interface_ospf.yaml
area:
config_get: 'show running interface all'
config_get_token
config_get_token can be a single string, a single regex, an array of strings,
or an array of regexs.
If this value is a string or array of strings, then the config_get command
will be executed to produce structured output and the string(s) will be
used as lookup keys.
WARNING: structured output, although elegant, may not be supported for all commands or all platforms. Use with caution.
# show_version.yaml
cpu:
config_get: 'show version'
config_get_token: 'cpu_name'
# config_get('show_version', 'cpu') returns structured_output['cpu_name']
# inventory.yaml
productid:
config_get: 'show inventory'
config_get_token: ['TABLE_inv', 'ROW_inv', 0, 'productid']
# config_get('inventory', 'productid') returns
# structured_output['TABLE_inv']['ROW_inv'][0]['productid']
If this value is a regexp or array of regexps, then the config_get command
will be executed to produce plaintext output.
For a single regexp, it will be used to match against the plaintext.
# memory.yaml
total:
config_get: 'show system resources'
config_get_token: '/Memory.* (\S+) total/'
# config_get('memory', 'total') returns
# plaintext_output.scan(/Memory.* (\S+) total/)
For an array of regex, then the plaintext is assumed to be hierarchical in
nature (like show running-config) and the regexs are used to filter down
through the hierarchy.
# interface.yaml
description:
config_get: 'show running interface all'
config_get_token: ['/^interface <name>$/i', '/^description (.*)/']
# config_get('interface', 'description', name: 'Ethernet1/1') gets the
# plaintext output, finds the subsection under /^interface Ethernet1/1$/i,
# then finds the line matching /^description (.*)$/ in that subsection
config_get_token_append
When using a _template section, an attribute can use
config_get_token_append to extend the config_get_token value provided by
the template instead of replacing it:
# interface.yaml
_template:
config_get: 'show running-config interface all'
config_get_token: '/^interface <name>$/i'
description:
config_get_token_append: '/^description (.*)$/'
# config_get_token value for 'description' is now:
# ['/^interface <name>$/i', '/^description (.*)$/']
This can also be used to specify conditional tokens which may or may not be
used depending on the set of parameters passed into config_get():
# ospf.yaml
_template:
config_get: 'show running ospf all'
config_get_token: '/^router ospf <name>$/'
config_get_token_append:
- '/^vrf <vrf>$/'
router_id:
config_get_token_append: '/^router-id (\S+)$/'
In this example, the vrf parameter is optional and a different
config_get_token value will be generated depending on its presence or absence:
irb(main):008:0> ref = cr.lookup('ospf', 'router_id')
irb(main):012:0> ref.config_get_token(name: 'red')
=> [/^router ospf red$/, /^router-id (\S+)?$/]
irb(main):013:0> ref.config_get_token(name: 'red', vrf: 'blue')
=> [/^router ospf red$/, /^vrf blue$/, /^router-id (\S+)?$/]
config_set
The config_set parameter is a string or array of strings representing the
configuration CLI command(s) used to set the value of the attribute.
# interface.yaml
create:
config_set: 'interface <name>'
description:
config_set: ['interface <name>', 'description <desc>']
config_set_append
When using a _template section, an attribute can use config_set_append to
extend the config_set value provided by the template instead of replacing it:
# interface.yaml
_template:
config_set: 'interface <name>'
access_vlan:
config_set_append: 'switchport access vlan <number>'
# config_set value for 'access_vlan' is now:
# ['interface <name>', 'switchport access vlan <number>']
Much like config_get_token_append, this can also be used to specify optional
commands that can be included or omitted as needed:
# ospf.yaml
_template:
config_set: 'router ospf <name>'
config_set_append:
- 'vrf <vrf>'
router_id:
config_set_append: 'router-id <router_id>'
irb(main):008:0> ref = cr.lookup('ospf', 'router_id')
irb(main):017:0> ref.config_set(name: 'red', state: nil, router_id: '1.1.1.1')
=> ["router ospf red", " router-id 1.1.1.1"]
irb(main):019:0> ref.config_set(name: 'red', vrf: 'blue',
state: 'no', router_id: '1.1.1.1')
=> ["router ospf red", "vrf blue", "no router-id 1.1.1.1"]
default_value
If there is a default value for this attribute when not otherwise specified by the user, the default_value parameter describes it. This can be a string, boolean, integer, array, or nil.
description:
default_value: ''
hello_interval:
default_value: 10
auto_cost:
default_value: [40, 'Gbps']
ipv4_address:
# YAML represents nil as ~
default_value: ~
By convention, a default_value of '' (empty string) represents a configurable property that defaults to absent, while a default of nil (Ruby) or ~ (YAML) represents a property that has no meaningful default at all.
config_get() will return the defined default_value if the defined config_get_token does not match anything on the node. Normally this is desirable behavior, but you can use auto_default to change this behavior if needed.
default_only
Some attributes may be hard-coded in such a way that they have a meaningful default value but no relevant config_get_token or config_set behavior. For such attributes, the key default_only should be used as an alternative to default_value. The benefit of using this key is that it causes the config_get() API to always return the default value and config_set() to raise a Cisco::UnsupportedError.
negotiate_auto_ethernet:
kind: boolean
cli_nexus:
/(N7|C3064)/:
# this feature is always off on these platforms and cannot be changed
default_only: false
else:
config_get_token_append: '/^(no )?negotiate auto$/'
config_set_append: "%s negotiate auto"
default_value: true
kind
The kind attribute is used to specify the type of value that is returned by config_get(). If unspecified, no attempt will be made to guess the return type and it will typically be one of string, array, or nil. If kind is specified, type conversion will automatically be performed as follows:
kind: boolean- value will be coerced totrue/false, and if nodefault_valueis set, anilresult will be returned asfalse.kind: int- value will be coerced to an integer, and if nodefault_valueis set, anilresult will be returned as0.kind: string- value will be coerced to a string, leading/trailing whitespace will be stripped, and if nodefault_valueis set, anilresult will be returned as''.
# interface.yaml
---
access_vlan:
config_get_token_append: '/^switchport access vlan (.*)$/'
config_set_append: "switchport access vlan %s"
kind: int
default_value: 1
description:
kind: string
config_get_token_append: '/^description (.*)/'
config_set_append: "%s description %s"
default_value: ""
feature_lacp:
kind: boolean
config_get: "show running | i ^feature"
config_get_token: '/^feature lacp$/'
config_set: "%s feature lacp"
multiple
By default, config_get_token should uniquely identify a single configuration entry, and config_get() will raise an error if more than one match is found. For a small number of attributes, it may be desirable to permit multiple matches (in particular, 'all_*' attributes that are used up to look up all interfaces, all VRFs, etc.). For such attributes, you must specify the key multiple:. When this key is present, config_get() will permit multiple matches and will return an array of matches (even if there is only a single match).
# interface.yaml
---
all_interfaces:
multiple:
config_get_token: '/^interface (.*)/'
auto_default
Normally, if config_get_token produces no match, config_get() will return the defined default_value for this attribute. For some attributes, this may not be desirable. Setting auto_default: false will force config_get() to return nil in the non-matching case instead.
# bgp_af.yaml
---
dampen_igp_metric:
# dampen_igp_metric defaults to nil (disabled),
# but its default numeric value when enabled is 600.
# If disabled, we want config_get() to return nil, not 600.
default_value: 600
auto_default: false
kind: int
config_get_token_append: '/^dampen-igp-metric (\d+)$/'
config_set_append: '<state> dampen-igp-metric <num>'
test_config_get and test_config_get_regex
Test-only equivalents to config_get and config_get_token - a show command
to be executed over telnet by the minitest unit test scripts, and a regex
(or array thereof) to match in the resulting plaintext output.
Should only be referenced by test scripts, never by a feature provider itself.
# show_version.yaml
boot_image:
test_config_get: 'show version | no-more'
test_config_get_regex: '/NXOS image file is: (.*)$/'
test_config_result
Test-only container for input-result pairs that might differ by platform. Should only be referenced by test scripts, never by a feature provider itself.
# vtp.yaml
version:
/N7/:
test_config_result:
3: 3
else:
test_config_result:
3: 'Cisco::CliError'
Style Guide
Please see YAML Best Practices.