Advanced Topics
Lineage Rules
Lineage rules are rules that are written in YAML. They allow users to seek out very specific sections of configurations or even seek out very generalized lines within a configuration. For example, suppose you just wanted to seek out interface descriptions. Your lineage rule would look like:
- lineage:
- startswith: interface
- startswith: description
In the above example, a start of a lineage is defined with the - lineage: syntax. From there the interface is defined with the - startswith: interface syntax under the - lineage: umbrella. This tells hier_config to search for any configuration that starts with the string interface as the parent of a configuration line. When it finds an interface parent, it then looks at any child configuration line of the interface that starts with the string description.
With lineage rules, you can get as deep into the children or as shallow as you need. Suppose you want to inspect the existence or absence of http, ssh, snmp, and logging within a configuration. This can be done with a single lineage rule, like so:
- lineage:
- startswith:
- ip ssh
- no ip ssh
- ip http
- no ip http
- snmp-server
- no snmp-server
- logging
- no logging
Or suppose, you want to inspect whether BGP IPv4 AFIs are activated. You can do this with the following:
- lineage:
- startswith: router bgp
- startswith: address-family ipv4
- endswith: activate
In the above example, I utilized a different keyword to look for activated BGP neighbors. The keywords that can be utilized within lineage rules are: - startswith - endswith - contains - equals - re_search
You can also put all of the above examples together in the same set of lineage rules like so:
- lineage:
- startswith: interface
- startswith: description
- lineage:
- startswith:
- ip ssh
- no ip ssh
- ip http
- no ip http
- snmp-server
- no snmp-server
- logging
- no logging
- lineage:
- startswith: router bgp
- startswith: address-family ipv4
- endswith: activate
When hier_config consumes the lineage rules, it consumes them as a list of lineage rules and processes them individually.
Working with Tags
With a firm understanding of lineage rules, more complex use cases become available within hier_config. A powerful use case is the ability to tag specific sections of configuration and only display remediations based on those tags. This becomes very handy when you're attempting to execute a maintenance that only targets low risk configuration changes or isolate the more risky configuration changes to scrutinize their execution during a maintenance.
Tagging expands on the use of the lineage rules by creating an add_tags keyword to a lineage rule.
Suppose you had a running configuration that had an ntp configuration that looked like:
ntp server 192.0.2.1 prefer version 2
However, your intended configuration utilized a publicly available NTP server on the internet:
ip name-server 1.1.1.1
ip name-server 8.8.8.8
ntp server time.nist.gov
You could create a lineage rule that targeted that specific remediation like this:
- lineage:
- startswith:
- ip name-server
- no ip name-server
- ntp
- no ntp
add_tags: ntp
Now we can modify the script above to load the tags and create a remediation of the said tags:
#!/usr/bin/env python3
# Import the hier_config Host library
from hier_config import Host
# Create a hier_config Host object
host = Host(hostname="aggr-example.rtr", os="ios")
# Load the tagged lineage rules
host.load_tags_from_file("./tests/fixtures/tags_ios.yml")
# Load a running configuration from a file
host.load_running_config_from_file("./tests/fixtures/running_config.conf")
# Load an intended configuration from a file
host.load_generated_config_from_file("./tests/fixtures/generated_config.conf")
# Create the remediation steps
host.remediation_config()
# Display the remediation steps for only the "ntp" tags
print(host.remediation_config_filtered_text(include_tags={"ntp"}, exclude_tags={}))
In the script, we made two changes. The first change is to load the tagged lineage rules:
host.load_tags_from_file("./tests/fixtures/tags_ios.yml")
.
And the second is to filter the remediation steps by only including steps that are tagged with ntp via the include_tags argument.
The remediation looks like:
no ntp server 192.0.2.1 prefer version 2
ip name-server 1.1.1.1
ip name-server 8.8.8.8
ntp server time.nist.gov
hier_config Options
There are a number of options that can be loaded into hier_config to make it better conform to the nuances of your network device. By default, hier_config loads a set of sane defaults for Cisco IOS, IOS XE, IOS XR, NX-OS, and Arista EOS.
Below are the configuration options available for manipulation.
base_options: dict = {
"style": None,
"sectional_overwrite": [],
"sectional_overwrite_no_negate": [],
"ordering": [],
"indent_adjust": [],
"parent_allows_duplicate_child": [],
"sectional_exiting": [],
"full_text_sub": [],
"per_line_sub": [],
"idempotent_commands_blacklist": [],
"idempotent_commands": [],
"negation_default_when": [],
"negation_negate_with": [],
}
The default options can be completely overwritten and loaded from a yaml file, or individual components of the options can be manipulated to provide the functionality that is desired.
Here is an example of manipulating the built-in options.
# Import the hier_config Host library
from hier_config import Host
# Create a hier_config Host object
host = Host(hostname="aggr-example.rtr", os="ios")
# Create an NTP negation ordered lineage rule
ordered_negate_ntp = {"lineage": [{"startswith": ["no ntp"], "order": 700}]}
# Update the hier_config options "ordering" key.
host.hconfig_options["ordering"].append(ordered_negate_ntp)
Here is an example of completely overwriting the default options and loading in your own.
# import YAML
import yaml
# Import the hier_config Host library
from hier_config import Host
# Load the hier_config options into memory
with open("./tests/fixtures/options_ios.yml") as f:
options = yaml.load(f.read(), Loader=yaml.SafeLoader)
# Create a hier_config Host object
host = Host(hostame="aggr-example.rtr", os="ios", hconfig_options=options)
In the following sections, I'll cover the available options.
style
The style defines the os family. Such as ios, iosxr, etc.
Example:
style: ios
sectional_overwrite_no_negate
The sectional overwrite with no negate hier_config option will completely overwrite sections of configuration without negating them. This option is often used with the RPL sections of IOS XR devices that require that the entire RPL be re-created when making modifications to them, rather than editing individual lines within the RPL.
An example of sectional overwrite with no negate is:
sectional_overwrite_no_negate:
- lineage:
- startswith: as-path-set
- lineage:
- startswith: prefix-set
- lineage:
- startswith: route-policy
- lineage:
- startswith: extcommunity-set
- lineage:
- startswith: community-set
sectional_overwrite
Sectional overwrite is just like sectional overwrite with no negate, except that hier_config will negate a section of configuration and then completely re-create it.
ordering
Ordering is one of the most useful hier_config options. This allows you to use lineage rules to define the order in which remediation steps are presented to the user. For the ntp example above, the ntp server was negated (no ntp server 192.0.2.1
) before the new ntp server was added. In most cases, this wouldn't be advantageous. Thus, ordering can be used to define the proper order to execute commands.
All commands are assigned a default order weight of 500, with a usable order weight of 1 - 999. The smaller the weight value, the higher on the list of steps a command is to be executed. The larger the weight value, the lower on the list of steps a command is to be executed. To create an order in which new ntp servers are added before old ntp servers are removed, you can create an order lineage that weights the negation to the bottom.
Example:
ordering:
- lineage:
- startswith: no ntp
order: 700
With the above order lineage applied, the output of the above ntp example would look like:
ip name-server 1.1.1.1
ip name-server 8.8.8.8
ntp server time.nist.gov
no ntp server 192.0.2.1 prefer version 2
indent_adjust
coming soon...
parent_allows_duplicate_child
coming soon...
sectional_exiting
Sectional exiting features configuration sections that have a configuration syntax that defines the end of a configuration section. Examples of this are RPL (route policy language) configurations in IOS XR or peer policy and peer session configurations in IOS BGP sections. The sectional exiting configuration allows you to define the configuration syntax so that hier_config can render a remediation that properly exits those configurations.
An example of sectional exiting is:
sectional_exiting:
- lineage:
- startswith: router bgp
- startswith: template peer-policy
exit_text: exit-peer-policy
- lineage:
- startswith: router bgp
- startswith: template peer-session
exit_text: exit-peer-session
full_text_sub
Full text sub allows for substitutions of a multi-line string. Regular expressions are commonly used and allowed in this section. An example of this would be:
full_text_sub:
- search: "banner\s(exec|motd)\s(\S)\n(.*\n){1,}(\2)"
replace: ""
This example simply searches for a banner message in the configuration and replaces it with an empty string.
per_line_sub
Per line sub allows for substitutions of individual lines. This is commonly used to remove artifacts from a running configuration that don't provide any value when creating remediation steps.
An example is removing lines such as:
Building configuration...
Current configuration : 3781 bytes
Per line sub can be used to remove those lines:
per_line_sub:
- search: "Building configuration.*"
replace: ""
- search: "Current configuration.*"
replace: ""
idempotent_commands_blacklist
coming soon...
idempotent_commands
Idempotent commands are commands that can just be overwritten and don't need negation. Lineage rules can be created to define those commands that are idempotent.
An example of idempotent commands are:
idempotent_commands:
- lineage:
- startswith: vlan
- startswith: name
- lineage:
- startswith: interface
- startswith: description
The lineage rules above specify that defining a vlan name and updating an interface description are both idempotent commands.
negation_default_when
coming soon...
negation_default_with
coming soon...
Custom hier_config Workflows
Coming soon...