Skip to content

Conversation

@jtdub
Copy link
Contributor

@jtdub jtdub commented Jun 11, 2025

closes #159

@jtdub jtdub requested review from aedwardstx and Copilot June 11, 2025 00:14
@jtdub jtdub self-assigned this Jun 11, 2025
@jtdub jtdub marked this pull request as draft June 11, 2025 00:14

This comment was marked as outdated.

@jtdub jtdub requested a review from Copilot June 12, 2025 00:33
@jtdub jtdub marked this pull request as ready for review June 12, 2025 00:37
@jtdub jtdub changed the title WIP | Add Fortigate FortiOS Driver Add Fortigate FortiOS Driver Jun 12, 2025

This comment was marked as outdated.

self, config: HConfigChild, other_children: Iterable[HConfigChild]
) -> Optional[HConfigChild]:
"""Override idempotent_for to only consider a config idempotent
if the same command exists in the other set.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should describe the set command idempotency convention being assumed in the docstring. Something like "FortiOS set command idempotency can be assumed by examining the first two words of the command. Take set subnet 1.1.1.1 255.255.255.0 and set subnet 2.2.2.1 255.255.255.0 for example, we only need to compare the first two words set subnet to establish idempotency. Other FortiOS set commands seem to follow the same convention."

),
],
parent_allows_duplicate_child=[
ParentAllowsDuplicateChildRule(match_rules=()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are some examples of duplicate commands at the root level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I reset the modifications that I made and comment out the parent_allows_duplicate_child section, I get:

__________________________________________ test_swap_negation ___________________________________________

    def test_swap_negation() -> None:
        platform = Platform.FORTINET_FORTIOS
>       running_config = get_hconfig_fast_load(
            platform,
            (
                "config system interface",
                "  edit port1",
                "    set description 'Port 1'",
                "    set status down",
                "  next",
                "end",
                "config system dns",
                "  set primary 192.0.2.1",
                "  set secondary 192.0.2.2",
                "end",
            ),
        )

tests/test_driver_fortinet_fortios.py:8: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
hier_config/constructors.py:151: in get_hconfig_fast_load
    most_recent_item, current_section = _analyze_indent(
hier_config/constructors.py:186: in _analyze_indent
    most_recent_item = current_section.add_child(line)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = HConfig(driver=HConfigDriverFortinetFortiOS, lines=('config system interface', '  edit port1', "    set description 'Port 1'", '    set status down', '  next', 'end', 'config system dns', '  set primary 192.0.2.1', '  set secondary 192.0.2.2'))
text = 'end'

    def add_child(
        self,
        text: str,
        *,
        return_if_present: bool = False,
        check_if_present: bool = True,
    ) -> HConfigChild:
        """Add a child instance of HConfigChild."""
        if not text:
            message = "text was empty"
            raise ValueError(message)
    
        if check_if_present and (child := self.children.get(text)):
            if self._is_duplicate_child_allowed():
                new_child = self.instantiate_child(text)
                self.children.append(new_child, update_mapping=False)
                return new_child
            if return_if_present:
                return child
            message = f"Found a duplicate section: {(*self.path(), text)}"
>           raise DuplicateChildError(message)
E           hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end',)

hier_config/base.py:88: DuplicateChildError
____________________________________________________________ test_idempotent_for ____________________________________________________________

    def test_idempotent_for() -> None:
        platform = Platform.FORTINET_FORTIOS
>       running_config = get_hconfig_fast_load(
            platform,
            (
                "config system interface",
                "  edit port1",
                "    set description 'Old Description'",
                "    set status up",
                "  next",
                "end",
                "config system dns",
                "  set primary 192.0.2.1",
                "  set secondary 192.0.2.2",
                "end",
            ),
        )

tests/test_driver_fortinet_fortios.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
hier_config/constructors.py:151: in get_hconfig_fast_load
    most_recent_item, current_section = _analyze_indent(
hier_config/constructors.py:186: in _analyze_indent
    most_recent_item = current_section.add_child(line)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = HConfig(driver=HConfigDriverFortinetFortiOS, lines=('config system interface', '  edit port1', "    set description 'Old Description'", '    set status up', '  next', 'end', 'config system dns', '  set primary 192.0.2.1', '  set secondary 192.0.2.2'))
text = 'end'

    def add_child(
        self,
        text: str,
        *,
        return_if_present: bool = False,
        check_if_present: bool = True,
    ) -> HConfigChild:
        """Add a child instance of HConfigChild."""
        if not text:
            message = "text was empty"
            raise ValueError(message)
    
        if check_if_present and (child := self.children.get(text)):
            if self._is_duplicate_child_allowed():
                new_child = self.instantiate_child(text)
                self.children.append(new_child, update_mapping=False)
                return new_child
            if return_if_present:
                return child
            message = f"Found a duplicate section: {(*self.path(), text)}"
>           raise DuplicateChildError(message)
E           hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end',)

hier_config/base.py:88: DuplicateChildError
========================================================== short test summary info ==========================================================
FAILED tests/test_driver_fortinet_fortios.py::test_swap_negation - hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end',)
FAILED tests/test_driver_fortinet_fortios.py::test_idempotent_for - hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end',)
======================================================= 2 failed, 88 passed in 0.98s ========================================================

@staticmethod
def _instantiate_rules() -> HConfigDriverRules:
return HConfigDriverRules(
sectional_exiting=[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would next need a sectional exiting rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next is the exit text for the edit sections`.

@jtdub
Copy link
Contributor Author

jtdub commented Jul 9, 2025

I confirmed that the DuplciateChild error is impacting XR.

% poetry run pytest tests/test_driver_cisco_xr.py -vvv 
========================================================= test session starts =========================================================
platform darwin -- Python 3.13.3, pytest-8.3.4, pluggy-1.5.0 -- /Users/jtdub/Library/Caches/pypoetry/virtualenvs/hier-config-v2ue-Plq-py3.13/bin/python
cachedir: .pytest_cache
rootdir: /Users/jtdub/Documents/code/hier_config
configfile: pyproject.toml
plugins: cov-6.0.0, profiling-1.8.1, xdist-3.6.1
collected 1 item                                                                                                                      

tests/test_driver_cisco_xr.py::test_duplicate_child FAILED                                                                      [100%]

============================================================== FAILURES ===============================================================
________________________________________________________ test_duplicate_child _________________________________________________________

    def test_duplicate_child() -> None:
        platform = Platform.CISCO_XR
>       running_config = get_hconfig_fast_load(
            platform,
            (
                "route-policy SET_COMMUNITY_AND_PERMIT",
                "  if destination in (192.0.2.0/24, 198.51.100.0/24) then",
                "    set community (65001:100) additive",
                "    pass",
                "  else",
                "    drop",
                "  endif",
                "end-policy",
                "",
                "route-policy SET_LOCAL_PREF_AND_PASS",
                "  if destination in (203.0.113.0/24) then",
                "    set local-preference 200",
                "    pass",
                "  else",
                "    drop",
                "  endif",
                "end-policy",
            ),
        )

tests/test_driver_cisco_xr.py:7: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
hier_config/constructors.py:151: in get_hconfig_fast_load
    most_recent_item, current_section = _analyze_indent(
hier_config/constructors.py:186: in _analyze_indent
    most_recent_item = current_section.add_child(line)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = HConfig(driver=HConfigDriverCiscoIOSXR, lines=('route-policy SET_COMMUNITY_AND_PERMIT', '  if destination in (192.0.2.0/24, 198.51.100.0/24) then', '    set community (65001:100) additive', '    pass', '  else', '    drop', '  endif', 'end-policy', 'route-policy SET_LOCAL_PREF_AND_PASS', '  if destination in (203.0.113.0/24) then', '    set local-preference 200', '    pass', '  else', '    drop', '  endif'))
text = 'end-policy'

    def add_child(
        self,
        text: str,
        *,
        return_if_present: bool = False,
        check_if_present: bool = True,
    ) -> HConfigChild:
        """Add a child instance of HConfigChild."""
        if not text:
            message = "text was empty"
            raise ValueError(message)
    
        if check_if_present and (child := self.children.get(text)):
            if self._is_duplicate_child_allowed():
                new_child = self.instantiate_child(text)
                self.children.append(new_child, update_mapping=False)
                return new_child
            if return_if_present:
                return child
            message = f"Found a duplicate section: {(*self.path(), text)}"
>           raise DuplicateChildError(message)
E           hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end-policy',)

hier_config/base.py:88: DuplicateChildError
======================================================= short test summary info =======================================================
FAILED tests/test_driver_cisco_xr.py::test_duplicate_child - hier_config.exceptions.DuplicateChildError: Found a duplicate section: ('end-policy',)
========================================================== 1 failed in 0.12s ==========================================================

@jtdub jtdub requested review from aedwardstx and Copilot July 10, 2025 01:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds initial support for Fortinet FortiOS in the configuration management library and refactors driver lookup.

  • Introduces Platform.FORTINET_FORTIOS and implements HConfigDriverFortinetFortiOS with custom rules and idempotency behavior.
  • Refactors get_hconfig_driver to use a mapping for platform‐to‐driver resolution.
  • Enhances get_hconfig_fast_load to apply per‐line substitutions before parsing, and adds tests for both FortiOS and Cisco XR.

Reviewed Changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_driver_fortinet_fortios.py New unit tests covering negation swapping, idempotency, future
tests/test_driver_cisco_xr.py New test for duplicate‐child removal in Cisco XR driver
hier_config/platforms/fortinet_fortios/driver.py New HConfigDriverFortinetFortiOS class with rule overrides
hier_config/models.py Added FORTINET_FORTIOS to Platform enum
hier_config/constructors.py Refactored get_hconfig_driver to use a dictionary and updated fast‐load parsing logic
Comments suppressed due to low confidence (2)

hier_config/constructors.py:35

  • [nitpick] The platform_drivers mapping is defined inline within get_hconfig_driver. For better readability and reuse, consider moving this dictionary to a module‐level constant.
    platform_drivers: dict[Platform, type[HConfigDriverBase]] = {

tests/test_driver_cisco_xr.py:1

  • [nitpick] This PR includes a new Cisco XR test without any corresponding driver changes for XR. It may be clearer to split that test into a separate PR or update the driver in the same change to keep concerns focused.
from hier_config import get_hconfig_fast_load

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Fortigate driver

3 participants