The Future Config feature, introduced in version 2.2.0, attempts to predict the state of the running configuration after a change is applied.
This feature is useful in scenarios where you need to determine the anticipated configuration state following a change, such as:
- Verifying that a configuration change was successfully applied to a device
- For example, checking if the post-change configuration matches the predicted future configuration
- Generating a future-state configuration that can be analyzed by tools like Batfish to assess the potential impact of a change
- Building rollback configurations: once the future configuration state is known, a rollback configuration can be generated by simply creating the remediation in reverse
(rollback = future.config_to_get_to(running)).- When building rollbacks for a series of configuration changes, you can use the future configuration from each change as input for the subsequent change. For example, use the future configuration after Change 1 as the input for determining the future configuration after Change 2, and so on.
HConfig.merge() and HConfig.future() both combine configuration data, but they serve different purposes:
merge()expects non-overlapping sections. If the same parent/child path already exists, Hier Config raiseshier_config.exceptions.DuplicateChildErrorto highlight the conflict. This behavior is helpful when you are stitching together independent remediation fragments (AAA, logging, SNMP, and so on) and need deterministic, fail-fast validation.future()mirrors how a device processes configuration input. Shared sections are merged recursively, later commands overwrite earlier leaf nodes, and the result reflects the post-change running configuration. Usefuture()when you want to preview layered changes or intentionally override existing settings.
If you receive a DuplicateChildError while calling merge(), consider whether your use case aligns better with future().
post_change_1_config = running_config.future(change_1_config)
change_1_rollback_config = post_change_1_config.config_to_get_to(running_config)
post_change_2_config = post_change_1_config.future(change_2_config)
change_2_rollback_config = post_change_2_config.config_to_get_to(post_change_1_config)The future() algorithm is a best-effort simulation. The following cases are not yet handled:
-
Negating a numbered ACL when removing a single entry. Removing one line from a numbered ACL (e.g.
no access-list 10 deny 10.0.0.0 0.255.255.255) requires the OS to track sequence numbers, which hier_config does not currently model. Workaround: Use extended named ACLs with sequence numbers so that hier_config can treat each entry independently via the ACL sequence-number stripping logic. -
Sectional exiting. The
future()algorithm does not emit sectional-exit tokens (e.g.exit-peer-policy) when reconstructing the predicted configuration. The output is structurally correct but may be missing closure tokens if you render it verbatim. -
Negate-with rules. When a command has a custom negation string defined by the driver (e.g.
NegationDefaultWithRule), thefuture()algorithm may not apply that string when processing removal of that command. -
Idempotent command avoid list. Commands matched by
IdempotentCommandsAvoidRuleshould be excluded from idempotency comparisons, butfuture()does not currently consult this list. -
ACL idempotency check. The extended ACL idempotency logic (sequence-number based matching) that drives
config_to_get_to()is not replicated infuture(). -
And likely others. Complex platform-specific interactions (e.g. VRF-aware BGP sections, route-policy inline templates on IOS XR) may produce imperfect results.
Tip: If you discover a gap, please open an issue and include a minimal reproducible example (running config + change config + expected future config). Contributions that extend
_future()to cover new cases are welcome.
Starting in version 3.4.0, Hier Config derives a structural "idempotency key" from
each command’s lineage when evaluating IdempotentCommandsRule.
This prevents unrelated lines that happen to share a prefix from being treated as
duplicates during future() predictions. For example, distinct BGP neighbor
descriptions such as neighbor 2.2.2.2 description neighbor2 and
neighbor 3.3.3.3 description neighbor3 now remain in the predicted future
configuration because their identities differ within the neighbor hierarchy.
Tip: When creating driver rules, ensure your
MatchRuledefinitions capture the structural parts of the command that should be unique (for instance, via regex capture groups or specificstartswithclauses). This allows the idempotency engine to distinguish between commands that only vary by attributes such as IP address or description text.
>>> from hier_config import get_hconfig, Platform
>>> from hier_config.utils import read_text_from_file
>>>
>>> running_config_text = read_text_from_file("./tests/fixtures/running_config.conf")
>>> generated_config_text = read_text_from_file("./tests/fixtures/remediation_config_without_tags.conf")
>>>
>>> running_config = get_hconfig(Platform.CISCO_IOS, running_config_text)
>>> remediation_config = get_hconfig(Platform.CISCO_IOS, remediation_config_text)
>>>
>>> print("Running Config")
Running Config
>>> for line in running_config.all_children():
... print(line.cisco_style_text())
...
hostname aggr-example.rtr
ip access-list extended TEST
10 permit ip 10.0.0.0 0.0.0.7 any
vlan 2
name switch_mgmt_10.0.2.0/24
vlan 3
name switch_mgmt_10.0.4.0/24
interface Vlan2
descripton switch_10.0.2.0/24
ip address 10.0.2.1 255.255.255.0
shutdown
interface Vlan3
mtu 9000
description switch_mgmt_10.0.4.0/24
ip address 10.0.4.1 255.255.0.0
ip access-group TEST in
no shutdown
>>>
>>> print("Remediation Config")
Remediation Config
>>> for line in remediation_config.all_children():
... print(line.cisco_style_text())
...
vlan 3
name switch_mgmt_10.0.3.0/24
vlan 4
name switch_mgmt_10.0.4.0/24
interface Vlan2
mtu 9000
ip access-group TEST in
no shutdown
interface Vlan3
description switch_mgmt_10.0.3.0/24
ip address 10.0.3.1 255.255.0.0
interface Vlan4
mtu 9000
description switch_mgmt_10.0.4.0/24
ip address 10.0.4.1 255.255.0.0
ip access-group TEST in
no shutdown
>>>
>>> print("Future Config")
Future Config
>>> for line in running_config.future(remediation_config).all_children():
... print(line.cisco_style_text())
...
vlan 3
name switch_mgmt_10.0.3.0/24
vlan 4
name switch_mgmt_10.0.4.0/24
interface Vlan2
mtu 9000
ip access-group TEST in
descripton switch_10.0.2.0/24
ip address 10.0.2.1 255.255.255.0
interface Vlan3
description switch_mgmt_10.0.3.0/24
ip address 10.0.3.1 255.255.0.0
mtu 9000
ip access-group TEST in
no shutdown
interface Vlan4
mtu 9000
description switch_mgmt_10.0.4.0/24
ip address 10.0.4.1 255.255.0.0
ip access-group TEST in
no shutdown
hostname aggr-example.rtr
ip access-list extended TEST
10 permit ip 10.0.0.0 0.0.0.7 any
vlan 2
name switch_mgmt_10.0.2.0/24
>>>HConfig.unified_diff() provides output similar to difflib.unified_diff() but with added awareness of out-of-order lines and parent-child relationships in the hier_config tree model.
This is useful when comparing configurations from two network devices (such as redundant pairs) or when validating differences between running and intended configurations.
Note: The unified diff algorithm shares the same limitations as
future()regarding duplicate child entries (e.g., multipleendifstatements in an IOS XR route-policy) and command ordering in sections where sequence matters (such as ACLs). For accurate ACL ordering, use sequence numbers.
>>> from hier_config import get_hconfig, Platform
>>> from pprint import pprint
>>>
>>> running_config_text = read_text_from_file("./tests/fixtures/running_config.conf")
>>> generated_config_text = read_text_from_file("./tests/fixtures/generated_config.conf")
>>>
>>> running_config = get_hconfig(Platform.CISCO_IOS, running_config_text)
>>> generated_config = get_hconfig(Platform.CISCO_IOS, generated_config_text)
>>>
>>> pprint(list(running_config.unified_diff(generated_config)))
['vlan 3',
' - name switch_mgmt_10.0.4.0/24',
' + name switch_mgmt_10.0.3.0/24',
'interface Vlan2',
' - shutdown',
' + mtu 9000',
' + ip access-group TEST in',
' + no shutdown',
'interface Vlan3',
' - description switch_mgmt_10.0.4.0/24',
' - ip address 10.0.4.1 255.255.0.0',
' + description switch_mgmt_10.0.3.0/24',
' + ip address 10.0.3.1 255.255.0.0',
'+ vlan 4',
' + name switch_mgmt_10.0.4.0/24',
'+ interface Vlan4',
' + mtu 9000',
' + description switch_mgmt_10.0.4.0/24',
' + ip address 10.0.4.1 255.255.0.0',
' + ip access-group TEST in',
' + no shutdown']
>>>Lines prefixed with + are present in generated_config but not in running_config; lines prefixed with - are present in running_config but not in generated_config. Parent lines without a prefix are shown as context only.